mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(data-table): minimal beginning implementation (#4468)
* feat(cdk-table): initial version * add files * remove unnecessary deps * add test * add tests * edit comments * minor style changes * changes due to comments * add test for column classes * add OnPush to rest of components * remove double quotes from colors.ts * lint fies * add comments to demo css * comments * split directives into separate files * lint * add content check in tests * renaming directives * remove fdescribe * rename private placeholders; adjust comments; change CollectionViewer * lint * change jasmine array param * remove unnecessary import from spec * move out of core; add console warning about instability * move jasmine-matchers * fix demo
This commit is contained in:
committed by
tinayuangao
parent
5c489497c3
commit
7db2762d78
@@ -0,0 +1,40 @@
|
||||
<div class="demo-table-container mat-elevation-z4">
|
||||
|
||||
<cdk-table #table [dataSource]="dataSource">
|
||||
|
||||
<!-- Column Definition: ID -->
|
||||
<ng-container cdkColumnDef="userId">
|
||||
<cdk-header-cell *cdkHeaderCellDef> ID </cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> {{row.id}} </cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Column Definition: Progress -->
|
||||
<ng-container cdkColumnDef="progress">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Progress </cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row">
|
||||
<div class="demo-progress-stat">{{row.progress}}%</div>
|
||||
<div class="demo-progress-indicator-container">
|
||||
<div class="demo-progress-indicator"
|
||||
[style.background]="row.progress > 50 ? 'green' : 'red'"
|
||||
[style.opacity]="getOpacity(row.progress)"
|
||||
[style.width.%]="row.progress"></div>
|
||||
</div>
|
||||
</cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Column Definition: Name -->
|
||||
<ng-container cdkColumnDef="userName">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Name </cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> {{row.name}} </cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Column Definition: Color -->
|
||||
<ng-container cdkColumnDef="color">
|
||||
<cdk-header-cell *cdkHeaderCellDef>Color</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row" [style.color]="row.color"> {{row.color}} </cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<cdk-header-row *cdkHeaderRowDef="propertiesToDisplay"></cdk-header-row>
|
||||
<cdk-row *cdkRowDef="let row; columns: propertiesToDisplay"></cdk-row>
|
||||
</cdk-table>
|
||||
</div>
|
||||
@@ -0,0 +1,89 @@
|
||||
/* Structure */
|
||||
.demo-table-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 800px;
|
||||
|
||||
// Table fills in the remaining area with a scroll
|
||||
.cdk-table {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Styles to make the demo's cdk-table match the material design spec
|
||||
* https://material.io/guidelines/components/data-tables.html
|
||||
*/
|
||||
.cdk-table {
|
||||
display: block;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.cdk-row, .cdk-header-row {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #ccc;
|
||||
align-items: center;
|
||||
height: 48px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.cdk-cell, .cdk-header-cell {
|
||||
width: 150px;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cdk-header-cell {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: rgba(0, 0, 0, 0.54);
|
||||
}
|
||||
|
||||
.cdk-cell {
|
||||
font-size: 13px;
|
||||
color: rgba(0, 0, 0, 0.87);
|
||||
}
|
||||
|
||||
/* Column and cell styles */
|
||||
.cdk-column-userId {
|
||||
max-width: 32px;
|
||||
}
|
||||
|
||||
.cdk-column-userName {
|
||||
max-width: 80px;
|
||||
}
|
||||
|
||||
.cdk-column-progress {
|
||||
&.cdk-cell {
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
.demo-progress-stat {
|
||||
width: 32px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.demo-progress-indicator-container {
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.demo-progress-indicator {
|
||||
border-radius: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cdk-column-color {
|
||||
max-width: 160px;
|
||||
|
||||
&.cdk-cell {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
font-family: cursive;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {PeopleDatabase} from './people-database';
|
||||
import {PersonDataSource} from './person-data-source';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'data-table-demo',
|
||||
templateUrl: 'data-table-demo.html',
|
||||
styleUrls: ['data-table-demo.css'],
|
||||
})
|
||||
export class DataTableDemo {
|
||||
dataSource: PersonDataSource;
|
||||
propertiesToDisplay = ['userId', 'userName', 'progress', 'color'];
|
||||
|
||||
constructor(private _peopleDatabase: PeopleDatabase) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.dataSource = new PersonDataSource(this._peopleDatabase);
|
||||
}
|
||||
|
||||
getOpacity(progress: number) {
|
||||
let distanceFromMiddle = Math.abs(50 - progress);
|
||||
return distanceFromMiddle / 50 + .3;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {NAMES} from '../dataset/names';
|
||||
import {COLORS} from '../dataset/colors';
|
||||
|
||||
export let LATEST_ID: number = 0;
|
||||
|
||||
export interface UserData {
|
||||
id: string;
|
||||
name: string;
|
||||
progress: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class PeopleDatabase {
|
||||
data: UserData[] = [];
|
||||
|
||||
constructor() {
|
||||
for (let i = 0; i < 100; i++) { this.addPerson(); }
|
||||
}
|
||||
|
||||
addPerson() {
|
||||
const name =
|
||||
NAMES[Math.round(Math.random() * (NAMES.length - 1))] + ' ' +
|
||||
NAMES[Math.round(Math.random() * (NAMES.length - 1))].charAt(0) + '.';
|
||||
|
||||
this.data.push({
|
||||
id: (++LATEST_ID).toString(),
|
||||
name: name,
|
||||
progress: Math.round(Math.random() * 100).toString(),
|
||||
color: COLORS[Math.round(Math.random() * (COLORS.length - 1))]
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import {CollectionViewer, DataSource} from '@angular/material';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {PeopleDatabase, UserData} from './people-database';
|
||||
|
||||
export class PersonDataSource extends DataSource<any> {
|
||||
_renderedData: any[] = [];
|
||||
|
||||
constructor(private _peopleDatabase: PeopleDatabase) {
|
||||
super();
|
||||
}
|
||||
|
||||
connect(collectionViewer: CollectionViewer): Observable<UserData[]> {
|
||||
return collectionViewer.viewChanged.map((view: {start: number, end: number}) => {
|
||||
// Set the rendered rows length to the virtual page size. Fill in the data provided
|
||||
// from the index start until the end index or pagination size, whichever is smaller.
|
||||
this._renderedData.length = this._peopleDatabase.data.length;
|
||||
|
||||
const buffer = 20;
|
||||
let rangeStart = Math.max(0, view.start - buffer);
|
||||
let rangeEnd = Math.min(this._peopleDatabase.data.length, view.end + buffer);
|
||||
|
||||
for (let i = rangeStart; i < rangeEnd; i++) {
|
||||
this._renderedData[i] = this._peopleDatabase.data[i];
|
||||
}
|
||||
|
||||
return this._renderedData;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
export const COLORS = [
|
||||
'AliceBlue',
|
||||
'AntiqueWhite',
|
||||
'Aqua',
|
||||
'Aquamarine',
|
||||
'Azure',
|
||||
'Beige',
|
||||
'Bisque',
|
||||
'Black',
|
||||
'BlanchedAlmond',
|
||||
'Blue',
|
||||
'BlueViolet',
|
||||
'Brown',
|
||||
'BurlyWood',
|
||||
'CadetBlue',
|
||||
'Chartreuse',
|
||||
'Chocolate',
|
||||
'Coral',
|
||||
'CornflowerBlue',
|
||||
'Cornsilk',
|
||||
'Crimson',
|
||||
'Cyan',
|
||||
'DarkBlue',
|
||||
'DarkCyan',
|
||||
'DarkGoldenRod',
|
||||
'DarkGray',
|
||||
'DarkGrey',
|
||||
'DarkGreen',
|
||||
'DarkKhaki',
|
||||
'DarkMagenta',
|
||||
'DarkOliveGreen',
|
||||
'Darkorange',
|
||||
'DarkOrchid',
|
||||
'DarkRed',
|
||||
'DarkSalmon',
|
||||
'DarkSeaGreen',
|
||||
'DarkSlateBlue',
|
||||
'DarkSlateGray',
|
||||
'DarkSlateGrey',
|
||||
'DarkTurquoise',
|
||||
'DarkViolet',
|
||||
'DeepPink',
|
||||
'DeepSkyBlue',
|
||||
'DimGray',
|
||||
'DimGrey',
|
||||
'DodgerBlue',
|
||||
'FireBrick',
|
||||
'FloralWhite',
|
||||
'ForestGreen',
|
||||
'Fuchsia',
|
||||
'Gainsboro',
|
||||
'GhostWhite',
|
||||
'Gold',
|
||||
'GoldenRod',
|
||||
'Gray',
|
||||
'Grey',
|
||||
'Green',
|
||||
'GreenYellow',
|
||||
'HoneyDew',
|
||||
'HotPink',
|
||||
'IndianRed',
|
||||
'Indigo',
|
||||
'Ivory',
|
||||
'Khaki',
|
||||
'Lavender',
|
||||
'LavenderBlush',
|
||||
'LawnGreen',
|
||||
'LemonChiffon',
|
||||
'LightBlue',
|
||||
'LightCoral',
|
||||
'LightCyan',
|
||||
'LightGoldenRodYellow',
|
||||
'LightGray',
|
||||
'LightGrey',
|
||||
'LightGreen',
|
||||
'LightPink',
|
||||
'LightSalmon',
|
||||
'LightSeaGreen',
|
||||
'LightSkyBlue',
|
||||
'LightSlateGray',
|
||||
'LightSlateGrey',
|
||||
'LightSteelBlue',
|
||||
'LightYellow',
|
||||
'Lime',
|
||||
'LimeGreen',
|
||||
'Linen',
|
||||
'Magenta',
|
||||
'Maroon',
|
||||
'MediumAquaMarine',
|
||||
'MediumBlue',
|
||||
'MediumOrchid',
|
||||
'MediumPurple',
|
||||
'MediumSeaGreen',
|
||||
'MediumSlateBlue',
|
||||
'MediumSpringGreen',
|
||||
'MediumTurquoise',
|
||||
'MediumVioletRed',
|
||||
'MidnightBlue',
|
||||
'MintCream',
|
||||
'MistyRose',
|
||||
'Moccasin',
|
||||
'NavajoWhite',
|
||||
'Navy',
|
||||
'OldLace',
|
||||
'Olive',
|
||||
'OliveDrab',
|
||||
'Orange',
|
||||
'OrangeRed',
|
||||
'Orchid',
|
||||
'PaleGoldenRod',
|
||||
'PaleGreen',
|
||||
'PaleTurquoise',
|
||||
'PaleVioletRed',
|
||||
'PapayaWhip',
|
||||
'PeachPuff',
|
||||
'Peru',
|
||||
'Pink',
|
||||
'Plum',
|
||||
'PowderBlue',
|
||||
'Purple',
|
||||
'Red',
|
||||
'RosyBrown',
|
||||
'RoyalBlue',
|
||||
'SaddleBrown',
|
||||
'Salmon',
|
||||
'SandyBrown',
|
||||
'SeaGreen',
|
||||
'SeaShell',
|
||||
'Sienna',
|
||||
'Silver',
|
||||
'SkyBlue',
|
||||
'SlateBlue',
|
||||
'SlateGray',
|
||||
'SlateGrey',
|
||||
'Snow',
|
||||
'SpringGreen',
|
||||
'SteelBlue',
|
||||
'Tan',
|
||||
'Teal',
|
||||
'Thistle',
|
||||
'Tomato',
|
||||
'Turquoise',
|
||||
'Violet',
|
||||
'Wheat',
|
||||
'White',
|
||||
'WhiteSmoke',
|
||||
'Yellow',
|
||||
'YellowGreen'
|
||||
];
|
||||
File diff suppressed because it is too large
Load Diff
@@ -37,14 +37,40 @@ import {PlatformDemo} from './platform/platform-demo';
|
||||
import {AutocompleteDemo} from './autocomplete/autocomplete-demo';
|
||||
import {InputDemo} from './input/input-demo';
|
||||
import {StyleDemo} from './style/style-demo';
|
||||
import {DataTableDemo} from './data-table/data-table-demo';
|
||||
import {PeopleDatabase} from './data-table/people-database';
|
||||
import {DatepickerDemo} from './datepicker/datepicker-demo';
|
||||
import {FullscreenOverlayContainer, OverlayContainer} from '@angular/material';
|
||||
import {
|
||||
MdAutocompleteModule, MdButtonModule, MdButtonToggleModule, MdCardModule, MdCheckboxModule,
|
||||
MdChipsModule, MdCoreModule, MdDatepickerModule, MdDialogModule, MdGridListModule, MdIconModule,
|
||||
MdInputModule, MdListModule, MdMenuModule, MdNativeDateModule, MdProgressBarModule, MdRadioModule,
|
||||
MdProgressSpinnerModule, MdRippleModule, MdSelectModule, MdSidenavModule, MdSliderModule,
|
||||
MdSlideToggleModule, MdSnackBarModule, MdTabsModule, MdToolbarModule, MdTooltipModule
|
||||
CdkDataTableModule,
|
||||
FullscreenOverlayContainer,
|
||||
MdAutocompleteModule,
|
||||
MdButtonModule,
|
||||
MdButtonToggleModule,
|
||||
MdCardModule,
|
||||
MdCheckboxModule,
|
||||
MdChipsModule,
|
||||
MdCoreModule,
|
||||
MdDatepickerModule,
|
||||
MdDialogModule,
|
||||
MdGridListModule,
|
||||
MdIconModule,
|
||||
MdInputModule,
|
||||
MdListModule,
|
||||
MdMenuModule,
|
||||
MdNativeDateModule,
|
||||
MdProgressBarModule,
|
||||
MdProgressSpinnerModule,
|
||||
MdRadioModule,
|
||||
MdRippleModule,
|
||||
MdSelectModule,
|
||||
MdSidenavModule,
|
||||
MdSliderModule,
|
||||
MdSlideToggleModule,
|
||||
MdSnackBarModule,
|
||||
MdTabsModule,
|
||||
MdToolbarModule,
|
||||
MdTooltipModule,
|
||||
OverlayContainer
|
||||
} from '@angular/material';
|
||||
|
||||
/**
|
||||
@@ -79,6 +105,7 @@ import {
|
||||
MdToolbarModule,
|
||||
MdTooltipModule,
|
||||
MdNativeDateModule,
|
||||
CdkDataTableModule,
|
||||
]
|
||||
})
|
||||
export class DemoMaterialModule {}
|
||||
@@ -102,6 +129,7 @@ export class DemoMaterialModule {}
|
||||
CardDemo,
|
||||
ChipsDemo,
|
||||
CheckboxDemo,
|
||||
DataTableDemo,
|
||||
DatepickerDemo,
|
||||
DemoApp,
|
||||
DemoAppOnPush,
|
||||
@@ -142,7 +170,8 @@ export class DemoMaterialModule {}
|
||||
PlatformDemo,
|
||||
],
|
||||
providers: [
|
||||
{provide: OverlayContainer, useClass: FullscreenOverlayContainer}
|
||||
{provide: OverlayContainer, useClass: FullscreenOverlayContainer},
|
||||
PeopleDatabase
|
||||
],
|
||||
entryComponents: [
|
||||
DemoApp,
|
||||
|
||||
@@ -40,6 +40,7 @@ export class DemoApp {
|
||||
{name: 'Card', route: 'card'},
|
||||
{name: 'Chips', route: 'chips'},
|
||||
{name: 'Checkbox', route: 'checkbox'},
|
||||
{name: 'Data Table', route: 'data-table'},
|
||||
{name: 'Datepicker', route: 'datepicker'},
|
||||
{name: 'Dialog', route: 'dialog'},
|
||||
{name: 'Gestures', route: 'gestures'},
|
||||
|
||||
@@ -33,6 +33,7 @@ import {AutocompleteDemo} from '../autocomplete/autocomplete-demo';
|
||||
import {InputDemo} from '../input/input-demo';
|
||||
import {StyleDemo} from '../style/style-demo';
|
||||
import {DatepickerDemo} from '../datepicker/datepicker-demo';
|
||||
import {DataTableDemo} from '../data-table/data-table-demo';
|
||||
|
||||
export const DEMO_APP_ROUTES: Routes = [
|
||||
{path: '', component: Home},
|
||||
@@ -40,6 +41,7 @@ export const DEMO_APP_ROUTES: Routes = [
|
||||
{path: 'button', component: ButtonDemo},
|
||||
{path: 'card', component: CardDemo},
|
||||
{path: 'chips', component: ChipsDemo},
|
||||
{path: 'data-table', component: DataTableDemo},
|
||||
{path: 'datepicker', component: DatepickerDemo},
|
||||
{path: 'radio', component: RadioDemo},
|
||||
{path: 'select', component: SelectDemo},
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import {ContentChild, Directive, ElementRef, Input, Renderer2, TemplateRef} from '@angular/core';
|
||||
|
||||
/**
|
||||
* Cell definition for a CDK data-table.
|
||||
* Captures the template of a column's data row cell as well as cell-specific properties.
|
||||
*/
|
||||
@Directive({selector: '[cdkCellDef]'})
|
||||
export class CdkCellDef {
|
||||
constructor(public template: TemplateRef<any>) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Header cell definition for a CDK data-table.
|
||||
* Captures the template of a column's header cell and as well as cell-specific properties.
|
||||
*/
|
||||
@Directive({selector: '[cdkHeaderCellDef]'})
|
||||
export class CdkHeaderCellDef {
|
||||
constructor(public template: TemplateRef<any>) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Column definition for the CDK data-table.
|
||||
* Defines a set of cells available for a table column.
|
||||
*/
|
||||
@Directive({selector: '[cdkColumnDef]'})
|
||||
export class CdkColumnDef {
|
||||
@Input('cdkColumnDef') name: string;
|
||||
|
||||
@ContentChild(CdkCellDef) cell: CdkCellDef;
|
||||
@ContentChild(CdkHeaderCellDef) headerCell: CdkHeaderCellDef;
|
||||
}
|
||||
|
||||
/** Header cell template container that adds the right classes and role. */
|
||||
@Directive({
|
||||
selector: 'cdk-header-cell',
|
||||
host: {
|
||||
'class': 'cdk-header-cell',
|
||||
'role': 'columnheader',
|
||||
},
|
||||
})
|
||||
export class CdkHeaderCell {
|
||||
constructor(private columnDef: CdkColumnDef,
|
||||
private elementRef: ElementRef,
|
||||
private renderer: Renderer2) {
|
||||
this.renderer.addClass(elementRef.nativeElement, `cdk-column-${columnDef.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** Cell template container that adds the right classes and role. */
|
||||
@Directive({
|
||||
selector: 'cdk-cell',
|
||||
host: {
|
||||
'class': 'cdk-cell',
|
||||
'role': 'gridcell',
|
||||
},
|
||||
})
|
||||
export class CdkCell {
|
||||
constructor(private columnDef: CdkColumnDef,
|
||||
private elementRef: ElementRef,
|
||||
private renderer: Renderer2) {
|
||||
this.renderer.addClass(elementRef.nativeElement, `cdk-column-${columnDef.name}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
|
||||
export interface CollectionViewer {
|
||||
viewChanged: Observable<{start: number, end: number}>;
|
||||
}
|
||||
|
||||
export abstract class DataSource<T> {
|
||||
abstract connect(collectionViewer: CollectionViewer): Observable<T[]>;
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {Component, ViewChild} from '@angular/core';
|
||||
import {CdkTable} from './data-table';
|
||||
import {CollectionViewer, DataSource} from './data-source';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
|
||||
import {customMatchers} from '../testing/jasmine-matchers';
|
||||
import {CdkDataTableModule} from './index';
|
||||
|
||||
describe('CdkTable', () => {
|
||||
let fixture: ComponentFixture<SimpleCdkTableApp>;
|
||||
|
||||
let component: SimpleCdkTableApp;
|
||||
let dataSource: FakeDataSource;
|
||||
let table: CdkTable;
|
||||
let tableElement: HTMLElement;
|
||||
|
||||
beforeEach(async(() => {
|
||||
jasmine.addMatchers(customMatchers);
|
||||
jasmine.addMatchers(tableCustomMatchers);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [CdkDataTableModule],
|
||||
declarations: [SimpleCdkTableApp],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(SimpleCdkTableApp);
|
||||
|
||||
component = fixture.componentInstance;
|
||||
dataSource = component.dataSource as FakeDataSource;
|
||||
table = component.table;
|
||||
tableElement = fixture.nativeElement.querySelector('cdk-table');
|
||||
|
||||
fixture.detectChanges(); // Let the component and table create embedded views
|
||||
fixture.detectChanges(); // Let the cells render
|
||||
}));
|
||||
|
||||
describe('should initialize', () => {
|
||||
it('with a connected data source', () => {
|
||||
expect(table.dataSource).toBe(dataSource);
|
||||
expect(dataSource.isConnected).toBe(true);
|
||||
});
|
||||
|
||||
it('with a rendered header with the right number of header cells', () => {
|
||||
const header = getHeaderRow(tableElement);
|
||||
|
||||
expect(header).not.toBe(undefined);
|
||||
expect(header.classList).toContain('customHeaderRowClass');
|
||||
expect(getHeaderCells(tableElement).length).toBe(component.columnsToRender.length);
|
||||
});
|
||||
|
||||
it('with rendered rows with right number of row cells', () => {
|
||||
const rows = getRows(tableElement);
|
||||
|
||||
expect(rows.length).toBe(dataSource.data.length);
|
||||
rows.forEach(row => {
|
||||
expect(row.classList).toContain('customRowClass');
|
||||
expect(getCells(row).length).toBe(component.columnsToRender.length);
|
||||
});
|
||||
});
|
||||
|
||||
it('with column class names provided to header and data row cells', () => {
|
||||
getHeaderCells(tableElement).forEach((headerCell, index) => {
|
||||
expect(headerCell.classList).toContain(`cdk-column-${component.columnsToRender[index]}`);
|
||||
});
|
||||
|
||||
getRows(tableElement).forEach(row => {
|
||||
getCells(row).forEach((cell, index) => {
|
||||
expect(cell.classList).toContain(`cdk-column-${component.columnsToRender[index]}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('with the right accessibility roles', () => {
|
||||
expect(tableElement).toBeRole('grid');
|
||||
|
||||
expect(getHeaderRow(tableElement)).toBeRole('row');
|
||||
getHeaderCells(tableElement).forEach(cell => expect(cell).toBeRole('columnheader'));
|
||||
|
||||
getRows(tableElement).forEach(row => {
|
||||
expect(row).toBeRole('row');
|
||||
getCells(row).forEach(cell => expect(cell).toBeRole('gridcell'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should re-render the rows when the data changes', () => {
|
||||
dataSource.addData();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getRows(tableElement).length).toBe(dataSource.data.length);
|
||||
|
||||
// Check that the number of cells is correct
|
||||
getRows(tableElement).forEach(row => {
|
||||
expect(getCells(row).length).toBe(component.columnsToRender.length);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO(andrewseguin): Add test for dynamic classes on header/rows
|
||||
|
||||
it('should match the right table content with dynamic data', () => {
|
||||
let initialDataLength = dataSource.data.length;
|
||||
expect(dataSource.data.length).toBe(3);
|
||||
let headerContent = ['Column A', 'Column B', 'Column C'];
|
||||
|
||||
let initialTableContent = [headerContent];
|
||||
dataSource.data.forEach(rowData => initialTableContent.push([rowData.a, rowData.b, rowData.c]));
|
||||
expect(tableElement).toMatchTableContent(initialTableContent);
|
||||
|
||||
// Add data to the table and recreate what the rendered output should be.
|
||||
dataSource.addData();
|
||||
expect(dataSource.data.length).toBe(initialDataLength + 1); // Make sure data was added
|
||||
fixture.detectChanges();
|
||||
fixture.detectChanges();
|
||||
|
||||
let changedTableContent = [headerContent];
|
||||
dataSource.data.forEach(rowData => changedTableContent.push([rowData.a, rowData.b, rowData.c]));
|
||||
expect(tableElement).toMatchTableContent(changedTableContent);
|
||||
});
|
||||
});
|
||||
|
||||
interface TestData {
|
||||
a: string;
|
||||
b: string;
|
||||
c: string;
|
||||
}
|
||||
|
||||
class FakeDataSource extends DataSource<TestData> {
|
||||
isConnected = false;
|
||||
|
||||
_dataChange = new BehaviorSubject<TestData[]>([]);
|
||||
set data(data: TestData[]) { this._dataChange.next(data); }
|
||||
get data() { return this._dataChange.getValue(); }
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
for (let i = 0; i < 3; i++) { this.addData(); }
|
||||
}
|
||||
|
||||
connect(collectionViewer: CollectionViewer): Observable<TestData[]> {
|
||||
this.isConnected = true;
|
||||
const streams = [collectionViewer.viewChanged, this._dataChange];
|
||||
return Observable.combineLatest(streams).map((results: any[]) => {
|
||||
const [view, data] = results;
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
addData() {
|
||||
const nextIndex = this.data.length + 1;
|
||||
|
||||
let copiedData = this.data.slice();
|
||||
copiedData.push({
|
||||
a: `a_${nextIndex}`,
|
||||
b: `b_${nextIndex}`,
|
||||
c: `c_${nextIndex}`
|
||||
});
|
||||
|
||||
this.data = copiedData;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<cdk-table [dataSource]="dataSource">
|
||||
<ng-container cdkColumnDef="column_a">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column A</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> {{row.a}}</cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="column_b">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column B</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> {{row.b}}</cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="column_c">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> {{row.c}}</cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<cdk-header-row class="customHeaderRowClass"
|
||||
*cdkHeaderRowDef="columnsToRender"></cdk-header-row>
|
||||
<cdk-row class="customRowClass"
|
||||
*cdkRowDef="let row; columns: columnsToRender"></cdk-row>
|
||||
</cdk-table>
|
||||
`
|
||||
})
|
||||
class SimpleCdkTableApp {
|
||||
dataSource: FakeDataSource = new FakeDataSource();
|
||||
columnsToRender = ['column_a', 'column_b', 'column_c'];
|
||||
|
||||
@ViewChild(CdkTable) table: CdkTable;
|
||||
}
|
||||
|
||||
function getElements(element: Element, query: string): Element[] {
|
||||
return [].slice.call(element.querySelectorAll(query));
|
||||
}
|
||||
|
||||
function getHeaderRow(tableElement: Element): Element {
|
||||
return tableElement.querySelector('.cdk-header-row');
|
||||
}
|
||||
|
||||
function getRows(tableElement: Element): Element[] {
|
||||
return getElements(tableElement, '.cdk-row');
|
||||
}
|
||||
function getCells(row: Element): Element[] {
|
||||
return row ? getElements(row, '.cdk-cell') : [];
|
||||
}
|
||||
|
||||
function getHeaderCells(tableElement: Element): Element[] {
|
||||
return getElements(getHeaderRow(tableElement), '.cdk-header-cell');
|
||||
}
|
||||
|
||||
const tableCustomMatchers: jasmine.CustomMatcherFactories = {
|
||||
toMatchTableContent: function(util, customEqualityTesters) {
|
||||
return {
|
||||
compare: function (tableElement: Element, expectedTableContent: any[]) {
|
||||
const missedExpectations = [];
|
||||
function checkCellContent(cell: Element, expectedTextContent: string) {
|
||||
const actualTextContent = cell.textContent.trim();
|
||||
if (actualTextContent !== expectedTextContent) {
|
||||
missedExpectations.push(
|
||||
`Expected cell contents to be ${expectedTextContent} but was ${actualTextContent}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check header cells
|
||||
const expectedHeaderContent = expectedTableContent.shift();
|
||||
getHeaderCells(tableElement).forEach((cell, index) => {
|
||||
return checkCellContent(cell, expectedHeaderContent[index]);
|
||||
});
|
||||
|
||||
// Check data row cells
|
||||
getRows(tableElement).forEach((row, rowIndex) => {
|
||||
getCells(row).forEach((cell, cellIndex) => {
|
||||
checkCellContent(cell, expectedTableContent[rowIndex][cellIndex]);
|
||||
});
|
||||
});
|
||||
|
||||
if (missedExpectations.length) {
|
||||
return {
|
||||
pass: false,
|
||||
message: missedExpectations.join('\n')
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
pass: true,
|
||||
message: 'Table contained the right content'
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,190 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ContentChild,
|
||||
ContentChildren,
|
||||
Directive,
|
||||
Input,
|
||||
QueryList,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
|
||||
import 'rxjs/add/operator/let';
|
||||
import 'rxjs/add/operator/debounceTime';
|
||||
import 'rxjs/add/observable/combineLatest';
|
||||
import {CollectionViewer, DataSource} from './data-source';
|
||||
import {CdkCellOutlet, CdkHeaderRowDef, CdkRowDef} from './row';
|
||||
import {CdkCellDef, CdkColumnDef, CdkHeaderCellDef} from './cell';
|
||||
|
||||
/**
|
||||
* Provides a handle for the table to grab the view container's ng-container to insert data rows.
|
||||
* @docs-private
|
||||
*/
|
||||
@Directive({selector: '[rowPlaceholder]'})
|
||||
export class RowPlaceholder {
|
||||
constructor(public viewContainer: ViewContainerRef) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a handle for the table to grab the view container's ng-container to insert the header.
|
||||
* @docs-private
|
||||
*/
|
||||
@Directive({selector: '[headerRowPlaceholder]'})
|
||||
export class HeaderRowPlaceholder {
|
||||
constructor(public viewContainer: ViewContainerRef) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* A data table that connects with a data source to retrieve data and renders
|
||||
* a header row and data rows. Updates the rows when new data is provided by the data source.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'cdk-table',
|
||||
template: `
|
||||
<ng-container headerRowPlaceholder></ng-container>
|
||||
<ng-container rowPlaceholder></ng-container>
|
||||
`,
|
||||
host: {
|
||||
'class': 'cdk-table',
|
||||
'role': 'grid' // TODO(andrewseguin): Allow the user to choose either grid or treegrid
|
||||
},
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CdkTable implements CollectionViewer {
|
||||
/**
|
||||
* Provides a stream containing the latest data array to render. Influenced by the table's
|
||||
* stream of view window (what rows are currently on screen).
|
||||
*/
|
||||
@Input() dataSource: DataSource<any>;
|
||||
|
||||
// TODO(andrewseguin): Remove max value as the end index
|
||||
// and instead calculate the view on init and scroll.
|
||||
/**
|
||||
* Stream containing the latest information on what rows are being displayed on screen.
|
||||
* Can be used by the data source to as a heuristic of what data should be provided.
|
||||
*/
|
||||
viewChanged =
|
||||
new BehaviorSubject<{start: number, end: number}>({start: 0, end: Number.MAX_VALUE});
|
||||
|
||||
/**
|
||||
* Map of all the user's defined columns identified by name.
|
||||
* Contains the header and data-cell templates.
|
||||
*/
|
||||
private _columnDefinitionsByName = new Map<string, CdkColumnDef>();
|
||||
|
||||
// Placeholders within the table's template where the header and data rows will be inserted.
|
||||
@ViewChild(RowPlaceholder) _rowPlaceholder: RowPlaceholder;
|
||||
@ViewChild(HeaderRowPlaceholder) _headerRowPlaceholder: HeaderRowPlaceholder;
|
||||
|
||||
/**
|
||||
* The column definitions provided by the user that contain what the header and cells should
|
||||
* render for each column.
|
||||
*/
|
||||
@ContentChildren(CdkColumnDef) _columnDefinitions: QueryList<CdkColumnDef>;
|
||||
|
||||
/** Template used as the header container. */
|
||||
@ContentChild(CdkHeaderRowDef) _headerDefinition: CdkHeaderRowDef;
|
||||
|
||||
/** Set of templates that used as the data row containers. */
|
||||
@ContentChildren(CdkRowDef) _rowDefinitions: QueryList<CdkRowDef>;
|
||||
|
||||
constructor(private _changeDetectorRef: ChangeDetectorRef) {
|
||||
console.warn('The data table is still in active development ' +
|
||||
'and should be considered unstable.');
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
// TODO(andrewseguin): Disconnect from the data source so
|
||||
// that it can unsubscribe from its streams.
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// TODO(andrewseguin): Setup a listener for scroll events
|
||||
// and emit the calculated view to this.viewChanged
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
// TODO(andrewseguin): Throw an error if two columns share the same name
|
||||
this._columnDefinitions.forEach(columnDef => {
|
||||
this._columnDefinitionsByName.set(columnDef.name, columnDef);
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
// TODO(andrewseguin): Re-render the header when the header's columns change.
|
||||
this.renderHeaderRow();
|
||||
|
||||
// TODO(andrewseguin): Re-render rows when their list of columns change.
|
||||
// TODO(andrewseguin): If the data source is not
|
||||
// present after view init, connect it when it is defined.
|
||||
// TODO(andrewseguin): Unsubscribe from this on destroy.
|
||||
this.dataSource.connect(this).subscribe((rowsData: any[]) => {
|
||||
// TODO(andrewseguin): Add a differ that will check if the data has changed,
|
||||
// rather than re-rendering all rows
|
||||
this._rowPlaceholder.viewContainer.clear();
|
||||
rowsData.forEach(rowData => this.insertRow(rowData));
|
||||
this._changeDetectorRef.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the embedded view for the header template and place it in the header row view container.
|
||||
*/
|
||||
renderHeaderRow() {
|
||||
const cells = this.getHeaderCellTemplatesForRow(this._headerDefinition);
|
||||
|
||||
// TODO(andrewseguin): add some code to enforce that exactly
|
||||
// one CdkCellOutlet was instantiated as a result
|
||||
// of `createEmbeddedView`.
|
||||
this._headerRowPlaceholder.viewContainer
|
||||
.createEmbeddedView(this._headerDefinition.template, {cells});
|
||||
CdkCellOutlet.mostRecentCellOutlet.cells = cells;
|
||||
CdkCellOutlet.mostRecentCellOutlet.context = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the embedded view for the data row template and place it in the correct index location
|
||||
* within the data row view container.
|
||||
*/
|
||||
insertRow(rowData: any) {
|
||||
// TODO(andrewseguin): Add when predicates to the row definitions
|
||||
// to find the right template to used based on
|
||||
// the data rather than choosing the first row definition.
|
||||
const row = this._rowDefinitions.first;
|
||||
|
||||
// TODO(andrewseguin): Add more context, such as first/last/isEven/etc
|
||||
const context = {$implicit: rowData};
|
||||
|
||||
// TODO(andrewseguin): add some code to enforce that exactly one
|
||||
// CdkCellOutlet was instantiated as a result of `createEmbeddedView`.
|
||||
this._rowPlaceholder.viewContainer.createEmbeddedView(row.template, context);
|
||||
|
||||
// Insert empty cells if there is no data to improve rendering time.
|
||||
CdkCellOutlet.mostRecentCellOutlet.cells = rowData ? this.getCellTemplatesForRow(row) : [];
|
||||
CdkCellOutlet.mostRecentCellOutlet.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cell template definitions to insert into the header
|
||||
* as defined by its list of columns to display.
|
||||
*/
|
||||
getHeaderCellTemplatesForRow(headerDef: CdkHeaderRowDef): CdkHeaderCellDef[] {
|
||||
return headerDef.columns.map(columnId => {
|
||||
return this._columnDefinitionsByName.get(columnId).headerCell;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cell template definitions to insert in the provided row
|
||||
* as defined by its list of columns to display.
|
||||
*/
|
||||
getCellTemplatesForRow(rowDef: CdkRowDef): CdkCellDef[] {
|
||||
return rowDef.columns.map(columnId => {
|
||||
return this._columnDefinitionsByName.get(columnId).cell;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {HeaderRowPlaceholder, RowPlaceholder, CdkTable} from './data-table';
|
||||
import {CdkCellOutlet, CdkHeaderRow, CdkHeaderRowDef, CdkRow, CdkRowDef} from './row';
|
||||
import {CdkColumnDef, CdkHeaderCellDef, CdkHeaderCell, CdkCell, CdkCellDef} from './cell';
|
||||
|
||||
export * from './data-source';
|
||||
export * from './data-table';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
exports: [
|
||||
CdkTable, CdkRowDef, CdkCellDef, CdkCellOutlet, CdkHeaderCellDef,
|
||||
CdkColumnDef, CdkCell, CdkRow,
|
||||
CdkHeaderCell, CdkHeaderRow, CdkHeaderRowDef],
|
||||
declarations: [
|
||||
CdkTable, CdkRowDef, CdkCellDef, CdkCellOutlet, CdkHeaderCellDef,
|
||||
CdkColumnDef, CdkCell, CdkRow,
|
||||
CdkHeaderCell, CdkHeaderRow, CdkHeaderRowDef,
|
||||
RowPlaceholder, HeaderRowPlaceholder,
|
||||
]
|
||||
|
||||
})
|
||||
export class CdkDataTableModule { }
|
||||
@@ -0,0 +1,90 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
Directive,
|
||||
Input,
|
||||
TemplateRef,
|
||||
ViewContainerRef
|
||||
} from '@angular/core';
|
||||
import {CdkCellDef} from './cell';
|
||||
|
||||
/**
|
||||
* Header row definition for the CDK data-table.
|
||||
* Captures the header row's template and other header properties such as the columns to display.
|
||||
*/
|
||||
@Directive({selector: '[cdkHeaderRowDef]'})
|
||||
export class CdkHeaderRowDef {
|
||||
@Input('cdkHeaderRowDef') columns: string[];
|
||||
|
||||
constructor(public template: TemplateRef<any>) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Data row definition for the CDK data-table.
|
||||
* Captures the header row's template and other row properties such as the columns to display.
|
||||
*/
|
||||
@Directive({selector: '[cdkRowDef]'})
|
||||
export class CdkRowDef {
|
||||
@Input('cdkRowDefColumns') columns: string[];
|
||||
|
||||
// TODO(andrewseguin): Add an input for providing a switch function to determine
|
||||
// if this template should be used.
|
||||
|
||||
constructor(public template: TemplateRef<any>) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Outlet for rendering cells inside of a row or header row.
|
||||
* @docs-private
|
||||
*/
|
||||
@Directive({selector: '[cdkCellOutlet]'})
|
||||
export class CdkCellOutlet {
|
||||
/** The ordered list of cells to render within this outlet's view container */
|
||||
cells: CdkCellDef[];
|
||||
|
||||
/** The data context to be provided to each cell */
|
||||
context: any;
|
||||
|
||||
/**
|
||||
* Static property containing the latest constructed instance of this class.
|
||||
* Used by the CDK data-table when each CdkHeaderRow and CdkRow component is created using
|
||||
* createEmbeddedView. After one of these components are created, this property will provide
|
||||
* a handle to provide that component's cells and context. After init, the CdkCellOutlet will
|
||||
* construct the cells with the provided context.
|
||||
*/
|
||||
static mostRecentCellOutlet: CdkCellOutlet = null;
|
||||
|
||||
constructor(private _viewContainer: ViewContainerRef) {
|
||||
CdkCellOutlet.mostRecentCellOutlet = this;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.cells.forEach(cell => {
|
||||
this._viewContainer.createEmbeddedView(cell.template, this.context);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Header template container that contains the cell outlet. Adds the right class and role. */
|
||||
@Component({
|
||||
selector: 'cdk-header-row',
|
||||
template: '<ng-container cdkCellOutlet></ng-container>',
|
||||
host: {
|
||||
'class': 'cdk-header-row',
|
||||
'role': 'row',
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CdkHeaderRow { }
|
||||
|
||||
/** Data row template container that contains the cell outlet. Adds the right class and role. */
|
||||
@Component({
|
||||
selector: 'cdk-row',
|
||||
template: '<ng-container cdkCellOutlet></ng-container>',
|
||||
host: {
|
||||
'class': 'cdk-row',
|
||||
'role': 'row',
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class CdkRow { }
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Collection of useful custom jasmine matchers for tests.
|
||||
*/
|
||||
export const customMatchers: jasmine.CustomMatcherFactories = {
|
||||
toBeRole: function(util: jasmine.MatchersUtil,
|
||||
customEqualityTesters: jasmine.CustomEqualityTester[]) {
|
||||
return {
|
||||
compare: function (element: Element, expectedRole: string) {
|
||||
const result: jasmine.CustomMatcherResult = {pass: false};
|
||||
result.pass = element.getAttribute('role') === expectedRole;
|
||||
result.message = `Expected role for ${element.tagName} to be ${expectedRole}`;
|
||||
|
||||
if (!result.pass) {
|
||||
result.message += ` but was ${expectedRole}`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
+7
-5
@@ -1,13 +1,13 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
|
||||
import {
|
||||
MdRippleModule,
|
||||
RtlModule,
|
||||
ObserveContentModule,
|
||||
PortalModule,
|
||||
OverlayModule,
|
||||
A11yModule,
|
||||
MdCommonModule,
|
||||
MdRippleModule,
|
||||
ObserveContentModule,
|
||||
OverlayModule,
|
||||
PortalModule,
|
||||
RtlModule
|
||||
} from './core/index';
|
||||
|
||||
import {MdButtonToggleModule} from './button-toggle/index';
|
||||
@@ -36,6 +36,7 @@ import {PlatformModule} from './core/platform/index';
|
||||
import {MdAutocompleteModule} from './autocomplete/index';
|
||||
import {StyleModule} from './core/style/index';
|
||||
import {MdDatepickerModule} from './datepicker/index';
|
||||
import {CdkDataTableModule} from './core/data-table/index';
|
||||
|
||||
const MATERIAL_MODULES = [
|
||||
MdAutocompleteModule,
|
||||
@@ -71,6 +72,7 @@ const MATERIAL_MODULES = [
|
||||
PlatformModule,
|
||||
MdCommonModule,
|
||||
ObserveContentModule,
|
||||
CdkDataTableModule
|
||||
];
|
||||
|
||||
/** @deprecated */
|
||||
|
||||
@@ -13,6 +13,7 @@ export * from './button-toggle/index';
|
||||
export * from './card/index';
|
||||
export * from './chips/index';
|
||||
export * from './checkbox/index';
|
||||
export * from './core/data-table/index';
|
||||
export * from './datepicker/index';
|
||||
export * from './dialog/index';
|
||||
export * from './grid-list/index';
|
||||
|
||||
Vendored
+8
@@ -1 +1,9 @@
|
||||
declare var module: {id: string};
|
||||
|
||||
/** Extends the interface for jasmine matchers to allow for custom matchers. */
|
||||
declare namespace jasmine {
|
||||
interface Matchers {
|
||||
toBeRole(expectedRole: string): boolean;
|
||||
toMatchTableContent(expectedContent: any[]): boolean;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,26 +24,30 @@ const ROLLUP_GLOBALS = {
|
||||
'@angular/cdk': 'ng.cdk',
|
||||
|
||||
// Rxjs dependencies
|
||||
'rxjs/Subject': 'Rx',
|
||||
'rxjs/add/observable/fromEvent': 'Rx.Observable',
|
||||
'rxjs/add/observable/combineLatest': 'Rx.Observable',
|
||||
'rxjs/add/observable/forkJoin': 'Rx.Observable',
|
||||
'rxjs/add/observable/of': 'Rx.Observable',
|
||||
'rxjs/add/observable/fromEvent': 'Rx.Observable',
|
||||
'rxjs/add/observable/merge': 'Rx.Observable',
|
||||
'rxjs/add/observable/of': 'Rx.Observable',
|
||||
'rxjs/add/observable/throw': 'Rx.Observable',
|
||||
'rxjs/add/operator/auditTime': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/toPromise': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/map': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/filter': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/do': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/share': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/finally': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/catch': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/debounceTime': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/do': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/filter': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/finally': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/first': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/let': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/map': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/share': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/startWith': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/switchMap': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/debounceTime': 'Rx.Observable.prototype',
|
||||
'rxjs/add/operator/toPromise': 'Rx.Observable.prototype',
|
||||
'rxjs/BehaviorSubject': 'Rx',
|
||||
'rxjs/Observable': 'Rx',
|
||||
'rxjs/Subject': 'Rx',
|
||||
'rxjs/Subscription': 'Rx',
|
||||
|
||||
};
|
||||
|
||||
export type BundleConfig = {
|
||||
|
||||
Reference in New Issue
Block a user