diff --git a/src/cdk-experimental/config.bzl b/src/cdk-experimental/config.bzl index ab32c89f144c..2570c021489f 100644 --- a/src/cdk-experimental/config.bzl +++ b/src/cdk-experimental/config.bzl @@ -6,6 +6,7 @@ CDK_EXPERIMENTAL_ENTRYPOINTS = [ "menu", "listbox", "popover-edit", + "table", "scrolling", "selection", "table-scroll-container", diff --git a/src/cdk-experimental/table/BUILD.bazel b/src/cdk-experimental/table/BUILD.bazel new file mode 100644 index 000000000000..1d6183c15362 --- /dev/null +++ b/src/cdk-experimental/table/BUILD.bazel @@ -0,0 +1,35 @@ +load("//tools:defaults.bzl", "ng_module", "ng_test_library", "ng_web_test_suite") + +package(default_visibility = ["//visibility:public"]) + +ng_module( + name = "table", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + module_name = "@angular/cdk-experimental/table", + deps = [ + "//src/cdk/table", + "@npm//@angular/common", + "@npm//@angular/core", + "@npm//rxjs", + ], +) + +ng_test_library( + name = "unit_test_sources", + srcs = glob( + ["**/*.spec.ts"], + exclude = ["**/*.e2e.spec.ts"], + ), + deps = [ + ":table", + "//src/cdk/table", + ], +) + +ng_web_test_suite( + name = "unit_tests", + deps = [":unit_test_sources"], +) diff --git a/src/cdk-experimental/table/index.ts b/src/cdk-experimental/table/index.ts new file mode 100644 index 000000000000..676ca90f1ffa --- /dev/null +++ b/src/cdk-experimental/table/index.ts @@ -0,0 +1,9 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './public-api'; diff --git a/src/cdk-experimental/table/public-api.ts b/src/cdk-experimental/table/public-api.ts new file mode 100644 index 000000000000..0f0b4879b5b4 --- /dev/null +++ b/src/cdk-experimental/table/public-api.ts @@ -0,0 +1,10 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './scrollable-table-body-layout'; +export * from './scrollable-table-body-module'; diff --git a/src/cdk-experimental/table/scrollable-table-body-layout.spec.ts b/src/cdk-experimental/table/scrollable-table-body-layout.spec.ts new file mode 100644 index 000000000000..a6cd17506b84 --- /dev/null +++ b/src/cdk-experimental/table/scrollable-table-body-layout.spec.ts @@ -0,0 +1,89 @@ +import {Component, Input, ViewChild} from '@angular/core'; +import {async, fakeAsync, TestBed} from '@angular/core/testing'; +import {CdkTable, CdkTableModule} from '@angular/cdk/table'; +import {CdkScrollableTableBodyModule} from './scrollable-table-body-module'; + + +describe('CdkScrollableTableBody', () => { + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [CdkScrollableTableBodyModule, CdkTableModule], + declarations: [CdkTableWithScrollableBody], + }).compileComponents(); + })); + + it('wraps row outlets in container', fakeAsync(() => { + const fixture = TestBed.createComponent(CdkTableWithScrollableBody); + const testComponent = fixture.componentInstance; + fixture.detectChanges(); + const table = testComponent.table; + const headerOutletContainer = table._headerRowOutlet.elementRef.nativeElement.parentElement; + const rowOutletContainer = table._rowOutlet.elementRef.nativeElement.parentElement; + const footerOutletContainer = table._footerRowOutlet.elementRef.nativeElement.parentElement; + testComponent.maxHeight = '100px'; + + expect(headerOutletContainer.classList.contains('cdk-table-scrollable-table-header')) + .toBe(true); + expect(rowOutletContainer.classList.contains('cdk-table-scrollable-table-body')) + .toBe(true); + expect(footerOutletContainer.classList.contains('cdk-table-scrollable-table-footer')) + .toBe(true); + })); + + it('updates DOM when max height is changed', fakeAsync(() => { + const fixture = TestBed.createComponent(CdkTableWithScrollableBody); + const testComponent = fixture.componentInstance; + fixture.detectChanges(); + const table = testComponent.table; + const rowOutletContainer = table._rowOutlet.elementRef.nativeElement.parentElement; + + testComponent.maxHeight = '100px'; + fixture.detectChanges(); + expect(rowOutletContainer.style.maxHeight).toBe('100px'); + + testComponent.maxHeight = '200px'; + fixture.detectChanges(); + expect(rowOutletContainer.style.maxHeight).toBe('200px'); + })); +}); + +interface TestData { + a: string; + b: string; + c: string; +} + +@Component({ + template: ` + + + Column A + {{row.a}} + Footer A + + + + Column B + {{row.b}} + Footer B + + + + Column C + {{row.c}} + Footer C + + + + + + + ` +}) +class CdkTableWithScrollableBody { + dataSource: []; + columnsToRender = ['column_a', 'column_b', 'column_c']; + + @Input() maxHeight!: string; + @ViewChild(CdkTable) table: CdkTable; +} diff --git a/src/cdk-experimental/table/scrollable-table-body-layout.ts b/src/cdk-experimental/table/scrollable-table-body-layout.ts new file mode 100644 index 000000000000..38e8892a4a72 --- /dev/null +++ b/src/cdk-experimental/table/scrollable-table-body-layout.ts @@ -0,0 +1,115 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Directive, Inject, Injectable, Input} from '@angular/core'; +import {CdkTable} from '@angular/cdk/table/table'; +import {DOCUMENT} from '@angular/common'; +import { + _TABLE_LAYOUT_STRATEGY, + _TableLayoutStrategy, + _StandardTableLayoutStrategy, +} from '@angular/cdk/table/table-layout-strategy'; + +/** + * A {@link _TableLayoutStrategy} that enables scrollable body content for flex tables. + */ +@Injectable() +export class ScrollableTableBodyLayoutStrategy implements _TableLayoutStrategy { + private readonly _document: Document; + private defaultLayout: _StandardTableLayoutStrategy; + private _pendingMaxHeight = 'none'; + private _scrollViewport?: HTMLElement; + readonly headerCssClass = 'cdk-table-scrollable-table-header'; + readonly bodyCssClass = 'cdk-table-scrollable-table-body'; + readonly footerCssClass = 'cdk-table-scrollable-table-footer'; + + constructor(@Inject(DOCUMENT) document: any) { + this._document = document; + this.defaultLayout = new _StandardTableLayoutStrategy(this._document); + } + + /** + * Returns the DOM structure for a native table. Scrollable body content is not supported for + * native tables. Return `null` to use the default {@link CdkTable} native table layout. + */ + getNativeLayout(table: CdkTable): DocumentFragment { + return this.defaultLayout.getNativeLayout(table); + } + + /** + * Returns the DOM structure for a flex table with scrollable body content. Each row outlet + * (header, body, footer) is wrapped in a separate container. The specified max height is applied + * to the body row outlet to make its content scrollable. + */ + getFlexLayout(table: CdkTable): DocumentFragment { + const documentFragment = this._document.createDocumentFragment(); + const sections = [ + {cssClass: this.headerCssClass, outlets: [table._headerRowOutlet]}, + {cssClass: this.bodyCssClass, outlets: [table._rowOutlet, table._noDataRowOutlet]}, + {cssClass: this.footerCssClass, outlets: [table._footerRowOutlet]}, + ]; + + for (const section of sections) { + const element = this._document.createElement('div'); + element.classList.add(section.cssClass); + for (const outlet of section.outlets) { + element.appendChild(outlet.elementRef.nativeElement); + } + + documentFragment.appendChild(element); + } + + this._scrollViewport = documentFragment.querySelector(`.${this.bodyCssClass}`) as HTMLElement; + this._scrollViewport!.style.overflow = 'auto'; + this._applyMaxHeight(this._scrollViewport!, this._pendingMaxHeight); + + return documentFragment; + } + + /** + * Show a scroll bar if the table's body exceeds this height. The height may be specified with + * any valid CSS unit of measurement. + */ + setMaxHeight(v: string) { + this._pendingMaxHeight = v; + if (this._scrollViewport) { + this._applyMaxHeight(this._scrollViewport, v); + } + } + + private _applyMaxHeight(el: HTMLElement, maxHeight: string) { + el.style.maxHeight = maxHeight; + } +} + +/** A directive that enables scrollable body content for flex tables. */ +@Directive({ + selector: 'cdk-table[scrollableBody]', + providers: [ + {provide: _TABLE_LAYOUT_STRATEGY, useClass: ScrollableTableBodyLayoutStrategy}, + ] +}) +export class CdkScrollableTableBody { + /** + * Show a scroll bar if the table's body exceeds this height. The height may be specified with + * any valid CSS unit of measurement. + */ + @Input('scrollableBody') + get maxHeight() { + return this._maxHeight; + } + set maxHeight(v: string) { + this._maxHeight = v; + this._layoutStrategy.setMaxHeight(v); + } + private _maxHeight = ''; + + constructor(@Inject(_TABLE_LAYOUT_STRATEGY) + private readonly _layoutStrategy: ScrollableTableBodyLayoutStrategy) { + } +} diff --git a/src/cdk-experimental/table/scrollable-table-body-module.ts b/src/cdk-experimental/table/scrollable-table-body-module.ts new file mode 100644 index 000000000000..0c687f39c547 --- /dev/null +++ b/src/cdk-experimental/table/scrollable-table-body-module.ts @@ -0,0 +1,22 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {NgModule} from '@angular/core'; +import {CdkScrollableTableBody} from './scrollable-table-body-layout'; + +export {CdkScrollableTableBody}; + +const EXPORTED_DECLARATIONS = [ + CdkScrollableTableBody, +]; + +@NgModule({ + exports: EXPORTED_DECLARATIONS, + declarations: EXPORTED_DECLARATIONS, +}) +export class CdkScrollableTableBodyModule { } diff --git a/src/cdk/table/table-layout-strategy.ts b/src/cdk/table/table-layout-strategy.ts new file mode 100644 index 000000000000..e37d90d386ac --- /dev/null +++ b/src/cdk/table/table-layout-strategy.ts @@ -0,0 +1,58 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Inject, InjectionToken} from '@angular/core'; +import {CdkTable} from '@angular/cdk/table/table'; +import {DOCUMENT} from '@angular/common'; + +/** Interface for a service that constructs the DOM structure for a {@link CdkTable}. */ +export interface _TableLayoutStrategy { + /** Constructs the DOM structure for a native table. */ + getNativeLayout(table: CdkTable): DocumentFragment; + /** Constructs the DOM structure for a flex table. */ + getFlexLayout(table: CdkTable): DocumentFragment; +} + +/** Injection token for {@link _TableLayoutStrategy}. */ +export const _TABLE_LAYOUT_STRATEGY = + new InjectionToken<_TableLayoutStrategy>('_TableLayoutStrategy'); + + +export class _StandardTableLayoutStrategy implements _TableLayoutStrategy { + private readonly _document: Document; + + constructor(@Inject(DOCUMENT) document: any) { + this._document = document; + } + + getNativeLayout(table: CdkTable): DocumentFragment { + const documentFragment = this._document.createDocumentFragment(); + const sections = [ + {tag: 'thead', outlets: [table._headerRowOutlet]}, + {tag: 'tbody', outlets: [table._rowOutlet, table._noDataRowOutlet]}, + {tag: 'tfoot', outlets: [table._footerRowOutlet]}, + ]; + + for (const section of sections) { + const element = this._document.createElement(section.tag); + element.setAttribute('role', 'rowgroup'); + + for (const outlet of section.outlets) { + element.appendChild(outlet.elementRef.nativeElement); + } + + documentFragment.appendChild(element); + } + + return documentFragment; + } + + getFlexLayout(table: CdkTable): DocumentFragment { + return this._document.createDocumentFragment(); + } +} diff --git a/src/cdk/table/table.ts b/src/cdk/table/table.ts index 634bb190bd12..1ae1ded3930e 100644 --- a/src/cdk/table/table.ts +++ b/src/cdk/table/table.ts @@ -81,6 +81,11 @@ import { } from './table-errors'; import {STICKY_POSITIONING_LISTENER, StickyPositioningListener} from './sticky-position-listener'; import {CDK_TABLE} from './tokens'; +import { + _StandardTableLayoutStrategy, + _TABLE_LAYOUT_STRATEGY, + _TableLayoutStrategy +} from './table-layout-strategy'; /** Interface used to provide an outlet for rows to be inserted into. */ export interface RowOutlet { @@ -486,22 +491,23 @@ export class CdkTable implements AfterContentChecked, CollectionViewer, OnDes protected readonly _elementRef: ElementRef, @Attribute('role') role: string, @Optional() protected readonly _dir: Directionality, @Inject(DOCUMENT) _document: any, private _platform: Platform, - /** * @deprecated `_coalescedStyleScheduler`, `_viewRepeater` and `_viewportRuler` * parameters to become required. * @breaking-change 11.0.0 */ @Optional() @Inject(_VIEW_REPEATER_STRATEGY) - protected readonly _viewRepeater?: _ViewRepeater, RowContext>, + protected readonly _viewRepeater?: _ViewRepeater, RowContext>, @Optional() @Inject(_COALESCED_STYLE_SCHEDULER) - protected readonly _coalescedStyleScheduler?: _CoalescedStyleScheduler, + protected readonly _coalescedStyleScheduler?: _CoalescedStyleScheduler, @Optional() @SkipSelf() @Inject(STICKY_POSITIONING_LISTENER) protected readonly _stickyPositioningListener?: StickyPositioningListener, // Optional for backwards compatibility. The viewport ruler is provided in root. Therefore, // this property will never be null. // tslint:disable-next-line: lightweight-tokens - @Optional() private readonly _viewportRuler?: ViewportRuler) { + @Optional() private readonly _viewportRuler?: ViewportRuler, + @Optional() @Inject(_TABLE_LAYOUT_STRATEGY) + @Optional() private readonly _layoutStrategy?: _TableLayoutStrategy|null) { if (!role) { this._elementRef.nativeElement.setAttribute('role', 'grid'); } @@ -512,10 +518,7 @@ export class CdkTable implements AfterContentChecked, CollectionViewer, OnDes ngOnInit() { this._setupStickyStyler(); - - if (this._isNativeHtmlTable) { - this._applyNativeTableSections(); - } + this._initTableLayout(); // Set up the trackBy function so that it uses the `RenderRow` as its identity by default. If // the user has provided a custom trackBy, return the result of that function as evaluated @@ -1153,28 +1156,14 @@ export class CdkTable implements AfterContentChecked, CollectionViewer, OnDes }); } - /** Adds native table sections (e.g. tbody) and moves the row outlets into them. */ - private _applyNativeTableSections() { - const documentFragment = this._document.createDocumentFragment(); - const sections = [ - {tag: 'thead', outlets: [this._headerRowOutlet]}, - {tag: 'tbody', outlets: [this._rowOutlet, this._noDataRowOutlet]}, - {tag: 'tfoot', outlets: [this._footerRowOutlet]}, - ]; - - for (const section of sections) { - const element = this._document.createElement(section.tag); - element.setAttribute('role', 'rowgroup'); - - for (const outlet of section.outlets) { - element.appendChild(outlet.elementRef.nativeElement); - } - - documentFragment.appendChild(element); - } + private _initTableLayout() { + const layoutStrategy = this._layoutStrategy || new _StandardTableLayoutStrategy(this._document); + const layout = this._isNativeHtmlTable + ? layoutStrategy.getNativeLayout(this) + : layoutStrategy.getFlexLayout(this); // Use a DocumentFragment so we don't hit the DOM on each iteration. - this._elementRef.nativeElement.appendChild(documentFragment); + this._elementRef.nativeElement.appendChild(layout); } /** diff --git a/src/components-examples/BUILD.bazel b/src/components-examples/BUILD.bazel index 9ad3bb8ad421..ff8380765d1c 100644 --- a/src/components-examples/BUILD.bazel +++ b/src/components-examples/BUILD.bazel @@ -47,6 +47,7 @@ ALL_EXAMPLES = [ "//src/components-examples/material-experimental/popover-edit", "//src/components-examples/material-experimental/mdc-card", "//src/components-examples/material-experimental/mdc-form-field", + "//src/components-examples/material-experimental/scrollable-table-body", "//src/components-examples/material-experimental/selection", "//src/components-examples/cdk/tree", "//src/components-examples/cdk/text-field", @@ -61,6 +62,7 @@ ALL_EXAMPLES = [ "//src/components-examples/cdk/overlay", "//src/components-examples/cdk-experimental/menu", "//src/components-examples/cdk-experimental/popover-edit", + "//src/components-examples/cdk-experimental/scrollable-table-body", "//src/components-examples/cdk-experimental/selection", ] diff --git a/src/components-examples/cdk-experimental/scrollable-table-body/BUILD.bazel b/src/components-examples/cdk-experimental/scrollable-table-body/BUILD.bazel new file mode 100644 index 000000000000..18422a173169 --- /dev/null +++ b/src/components-examples/cdk-experimental/scrollable-table-body/BUILD.bazel @@ -0,0 +1,27 @@ +load("//tools:defaults.bzl", "ng_module") + +package(default_visibility = ["//visibility:public"]) + +ng_module( + name = "scrollable-table-body", + srcs = glob(["**/*.ts"]), + assets = glob([ + "**/*.html", + "**/*.css", + ]), + module_name = "@angular/components-examples/cdk-experimental/scrollable-table-body", + deps = [ + "//src/cdk-experimental/table", + "//src/cdk/table", + "//src/material/button", + ], +) + +filegroup( + name = "source-files", + srcs = glob([ + "**/*.html", + "**/*.css", + "**/*.ts", + ]), +) diff --git a/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.css b/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.css new file mode 100644 index 000000000000..f0d18e4f1d11 --- /dev/null +++ b/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.css @@ -0,0 +1,10 @@ +/** + * Add basic flex styling so that the cells evenly space themselves in the row. + */ +cdk-row, cdk-header-row, cdk-footer-row { + display: flex; +} + +cdk-cell, cdk-header-cell, cdk-footer-cell { + flex: 1; +} diff --git a/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.html b/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.html new file mode 100644 index 000000000000..5f166045434e --- /dev/null +++ b/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.html @@ -0,0 +1,33 @@ + + + + +

Max Height: {{maxHeight}}

+ + + + No. + {{element.position}} + + + + + Name + {{element.name}} + + + + + Weight + {{element.weight}} + + + + + Symbol + {{element.symbol}} + + + + + diff --git a/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.ts b/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.ts new file mode 100644 index 000000000000..4d225684be1d --- /dev/null +++ b/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.ts @@ -0,0 +1,88 @@ +import {DataSource} from '@angular/cdk/collections'; +import {Component} from '@angular/core'; +import {BehaviorSubject, Observable} from 'rxjs'; + +export interface PeriodicElement { + name: string; + position: number; + symbol: string; + weight: number; +} + +const ELEMENT_DATA: PeriodicElement[] = [ + {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'}, + {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'}, + {position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'}, + {position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'}, + {position: 5, name: 'Boron', weight: 10.811, symbol: 'B'}, + {position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'}, + {position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'}, + {position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'}, + {position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'}, + {position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'}, +]; + +/** + * @title Example of {@link CdkScrollableTableBody} for the CDK flex table. + */ +@Component({ + selector: 'cdk-scrollable-table-body-flex-example', + styleUrls: ['cdk-scrollable-table-body-flex-example.css'], + templateUrl: 'cdk-scrollable-table-body-flex-example.html', +}) +export class CdkScrollableTableBodyFlexExample { + private readonly _allowedMaxHeights = ['100px', '200px', '400px', '800px']; + private _maxHeightIndex = 1; + displayedColumns: string[] = ['position', 'name', 'weight', 'symbol']; + dataSource = new ExampleDataSource(); + + get maxHeight() { + return this._allowedMaxHeights[this._maxHeightIndex]; + } + + addRow() { + this.dataSource.addRow(); + } + + removeRow() { + this.dataSource.removeRow(); + } + + nextMaxHeight() { + this._maxHeightIndex = (this._maxHeightIndex + 1) % this._allowedMaxHeights.length; + } +} + +/** + * Data source to provide what data should be rendered in the table. Note that the data source + * can retrieve its data in any way. In this case, the data source is provided a reference + * to a common data base, ExampleDatabase. It is not the data source's responsibility to manage + * the underlying data. Instead, it only needs to take the data and send the table exactly what + * should be rendered. + */ +export class ExampleDataSource extends DataSource { + private _data = [...ELEMENT_DATA]; + /** Stream of data that is provided to the table. */ + data = new BehaviorSubject(this._data); + + addRow() { + const row = {...ELEMENT_DATA[this._data.length % ELEMENT_DATA.length]}; + row.position = this._data.length + 1; + this._data.push(row); + this.data.next(this._data); + } + + removeRow() { + this._data.pop(); + this.data.next(this._data); + } + + /** Connect function called by the table to retrieve one stream containing the data to render. */ + connect(): Observable { + return this.data; + } + + disconnect() { + this.data.complete(); + } +} diff --git a/src/components-examples/cdk-experimental/scrollable-table-body/index.ts b/src/components-examples/cdk-experimental/scrollable-table-body/index.ts new file mode 100644 index 000000000000..2fdb7cb4d131 --- /dev/null +++ b/src/components-examples/cdk-experimental/scrollable-table-body/index.ts @@ -0,0 +1,30 @@ +import {NgModule} from '@angular/core'; +import {CdkTableModule} from '@angular/cdk/table'; +import { + CdkScrollableTableBodyModule, +} from '@angular/cdk-experimental/table/scrollable-table-body-module'; +import { + CdkScrollableTableBodyFlexExample, +} from './cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example'; +import {MatButtonModule} from '@angular/material/button'; + +export { + CdkScrollableTableBodyFlexExample, +}; + +const EXAMPLES = [ + CdkScrollableTableBodyFlexExample, +]; + +@NgModule({ + imports: [ + CdkScrollableTableBodyModule, + CdkTableModule, + MatButtonModule, + ], + declarations: EXAMPLES, + exports: EXAMPLES, + entryComponents: EXAMPLES, +}) +export class CdkScrollableTableBodyExamplesModule { +} diff --git a/src/components-examples/material-experimental/scrollable-table-body/BUILD.bazel b/src/components-examples/material-experimental/scrollable-table-body/BUILD.bazel new file mode 100644 index 000000000000..3ffa897f3254 --- /dev/null +++ b/src/components-examples/material-experimental/scrollable-table-body/BUILD.bazel @@ -0,0 +1,28 @@ +load("//tools:defaults.bzl", "ng_module") + +package(default_visibility = ["//visibility:public"]) + +ng_module( + name = "scrollable-table-body", + srcs = glob(["**/*.ts"]), + assets = glob([ + "**/*.html", + "**/*.css", + ]), + module_name = "@angular/components-examples/material-experimental/scrollable-table-body", + deps = [ + "//src/cdk-experimental/table", + "//src/material-experimental/table", + "//src/material/button", + "//src/material/table", + ], +) + +filegroup( + name = "source-files", + srcs = glob([ + "**/*.html", + "**/*.css", + "**/*.ts", + ]), +) diff --git a/src/components-examples/material-experimental/scrollable-table-body/index.ts b/src/components-examples/material-experimental/scrollable-table-body/index.ts new file mode 100644 index 000000000000..d02746454046 --- /dev/null +++ b/src/components-examples/material-experimental/scrollable-table-body/index.ts @@ -0,0 +1,32 @@ +import {NgModule} from '@angular/core'; +import { + CdkScrollableTableBodyModule, +} from '@angular/cdk-experimental/table/scrollable-table-body-module'; +import { + MatScrollableTableBodyFlexExample, +} from './mat-scrollable-table-body-flex/mat-scrollable-table-body-flex-example'; +import {MatTableModule} from '@angular/material/table'; +import {MatButtonModule} from '@angular/material/button'; +import {MatScrollableTableBodyModule} from '@angular/material-experimental/table'; + +export { + MatScrollableTableBodyFlexExample, +}; + +const EXAMPLES = [ + MatScrollableTableBodyFlexExample, +]; + +@NgModule({ + imports: [ + CdkScrollableTableBodyModule, + MatScrollableTableBodyModule, + MatButtonModule, + MatTableModule, + ], + declarations: EXAMPLES, + exports: EXAMPLES, + entryComponents: EXAMPLES, +}) +export class MatScrollableTableBodyExamplesModule { +} diff --git a/src/components-examples/material-experimental/scrollable-table-body/mat-scrollable-table-body-flex/mat-scrollable-table-body-flex-example.html b/src/components-examples/material-experimental/scrollable-table-body/mat-scrollable-table-body-flex/mat-scrollable-table-body-flex-example.html new file mode 100644 index 000000000000..9aa81028b3ea --- /dev/null +++ b/src/components-examples/material-experimental/scrollable-table-body/mat-scrollable-table-body-flex/mat-scrollable-table-body-flex-example.html @@ -0,0 +1,33 @@ + + + + +

Max Height: {{maxHeight}}

+ + + + No. + {{element.position}} + + + + + Name + {{element.name}} + + + + + Weight + {{element.weight}} + + + + + Symbol + {{element.symbol}} + + + + + diff --git a/src/components-examples/material-experimental/scrollable-table-body/mat-scrollable-table-body-flex/mat-scrollable-table-body-flex-example.ts b/src/components-examples/material-experimental/scrollable-table-body/mat-scrollable-table-body-flex/mat-scrollable-table-body-flex-example.ts new file mode 100644 index 000000000000..7c4301c91d6a --- /dev/null +++ b/src/components-examples/material-experimental/scrollable-table-body/mat-scrollable-table-body-flex/mat-scrollable-table-body-flex-example.ts @@ -0,0 +1,87 @@ +import {DataSource} from '@angular/cdk/collections'; +import {Component} from '@angular/core'; +import {BehaviorSubject, Observable} from 'rxjs'; + +export interface PeriodicElement { + name: string; + position: number; + symbol: string; + weight: number; +} + +const ELEMENT_DATA: PeriodicElement[] = [ + {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'}, + {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'}, + {position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'}, + {position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'}, + {position: 5, name: 'Boron', weight: 10.811, symbol: 'B'}, + {position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'}, + {position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'}, + {position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'}, + {position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'}, + {position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'}, +]; + +/** + * @title Example of {@link CdkScrollableTableBody} for the material flex table. + */ +@Component({ + selector: 'mat-scrollable-table-body-flex-example', + templateUrl: 'mat-scrollable-table-body-flex-example.html', +}) +export class MatScrollableTableBodyFlexExample { + private readonly _allowedMaxHeights = ['100px', '200px', '400px', '800px']; + private _maxHeightIndex = 1; + displayedColumns: string[] = ['position', 'name', 'weight', 'symbol']; + dataSource = new ExampleDataSource(); + + get maxHeight() { + return this._allowedMaxHeights[this._maxHeightIndex]; + } + + addRow() { + this.dataSource.addRow(); + } + + removeRow() { + this.dataSource.removeRow(); + } + + nextMaxHeight() { + this._maxHeightIndex = (this._maxHeightIndex + 1) % this._allowedMaxHeights.length; + } +} + +/** + * Data source to provide what data should be rendered in the table. Note that the data source + * can retrieve its data in any way. In this case, the data source is provided a reference + * to a common data base, ExampleDatabase. It is not the data source's responsibility to manage + * the underlying data. Instead, it only needs to take the data and send the table exactly what + * should be rendered. + */ +export class ExampleDataSource extends DataSource { + private _data = [...ELEMENT_DATA]; + /** Stream of data that is provided to the table. */ + data = new BehaviorSubject(this._data); + + addRow() { + const row = {...ELEMENT_DATA[this._data.length % ELEMENT_DATA.length]}; + row.position = this._data.length + 1; + this._data.push(row); + this.data.next(this._data); + } + + removeRow() { + this._data.pop(); + this.data.next(this._data); + } + + /** Connect function called by the table to retrieve one stream containing the data to render. */ + connect(): Observable { + return this.data; + } + + disconnect() { + this.data.complete(); + } +} diff --git a/src/dev-app/BUILD.bazel b/src/dev-app/BUILD.bazel index 0ba4c94d89ab..dcc6cf3e5b01 100644 --- a/src/dev-app/BUILD.bazel +++ b/src/dev-app/BUILD.bazel @@ -78,6 +78,7 @@ ng_module( "//src/dev-app/radio", "//src/dev-app/ripple", "//src/dev-app/screen-type", + "//src/dev-app/scrollable-table-body", "//src/dev-app/select", "//src/dev-app/selection", "//src/dev-app/sidenav", diff --git a/src/dev-app/dev-app/dev-app-layout.ts b/src/dev-app/dev-app/dev-app-layout.ts index 985c4e00f19b..97a68589e17e 100644 --- a/src/dev-app/dev-app/dev-app-layout.ts +++ b/src/dev-app/dev-app/dev-app-layout.ts @@ -66,6 +66,7 @@ export class DevAppLayout { {name: 'Radio', route: '/radio'}, {name: 'Ripple', route: '/ripple'}, {name: 'Screen Type', route: '/screen-type'}, + {name: 'Scrollable table body', route: '/scrollable-table-body'}, {name: 'Select', route: '/select'}, {name: 'Selection', route: '/selection'}, {name: 'Sidenav', route: '/sidenav'}, diff --git a/src/dev-app/dev-app/routes.ts b/src/dev-app/dev-app/routes.ts index 1f001264fe43..6b5e5adbf67f 100644 --- a/src/dev-app/dev-app/routes.ts +++ b/src/dev-app/dev-app/routes.ts @@ -130,6 +130,11 @@ export const DEV_APP_ROUTES: Routes = [ }, {path: 'radio', loadChildren: 'radio/radio-demo-module#RadioDemoModule'}, {path: 'ripple', loadChildren: 'ripple/ripple-demo-module#RippleDemoModule'}, + { + path: 'scrollable-table-body', + loadChildren: + 'scrollable-table-body/scrollable-table-body-demo-module#ScrollableTableBodyDemoModule' + }, {path: 'select', loadChildren: 'select/select-demo-module#SelectDemoModule'}, {path: 'sidenav', loadChildren: 'sidenav/sidenav-demo-module#SidenavDemoModule'}, { diff --git a/src/dev-app/scrollable-table-body/BUILD b/src/dev-app/scrollable-table-body/BUILD new file mode 100644 index 000000000000..9aef1e71faac --- /dev/null +++ b/src/dev-app/scrollable-table-body/BUILD @@ -0,0 +1,14 @@ +load("//tools:defaults.bzl", "ng_module") + +package(default_visibility = ["//visibility:public"]) + +ng_module( + name = "scrollable-table-body", + srcs = glob(["**/*.ts"]), + deps = [ + "//src/components-examples/material-experimental/scrollable-table-body", + "//src/dev-app/example", + "@npm//@angular/forms", + "@npm//@angular/router", + ], +) diff --git a/src/dev-app/scrollable-table-body/scrollable-table-body-demo-module.ts b/src/dev-app/scrollable-table-body/scrollable-table-body-demo-module.ts new file mode 100644 index 000000000000..60f7c25b2216 --- /dev/null +++ b/src/dev-app/scrollable-table-body/scrollable-table-body-demo-module.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {NgModule} from '@angular/core'; +import {RouterModule} from '@angular/router'; +import {ScrollableTableBodyDemo} from './scrollable-table-body-demo'; +/*import { + CdkScrollableTableBodyExamplesModule, +} from '@angular/components-examples/cdk-experimental/scrollable-table-body';*/ +import { + MatScrollableTableBodyExamplesModule, +} from '@angular/components-examples/material-experimental/scrollable-table-body'; + +@NgModule({ + imports: [ + // CdkScrollableTableBodyExamplesModule, + MatScrollableTableBodyExamplesModule, + RouterModule.forChild([{path: '', component: ScrollableTableBodyDemo}]), + ], + declarations: [ScrollableTableBodyDemo], +}) +export class ScrollableTableBodyDemoModule { +} diff --git a/src/dev-app/scrollable-table-body/scrollable-table-body-demo.ts b/src/dev-app/scrollable-table-body/scrollable-table-body-demo.ts new file mode 100644 index 000000000000..5f6852d0852f --- /dev/null +++ b/src/dev-app/scrollable-table-body/scrollable-table-body-demo.ts @@ -0,0 +1,20 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Component} from '@angular/core'; + +@Component({ + template: ` +

CDK scrollable-table-body with flex table

+ + +

Material scrollable-table-body with flex table

+ + `, +}) +export class ScrollableTableBodyDemo {} diff --git a/src/material-experimental/config.bzl b/src/material-experimental/config.bzl index 1006febdc775..eca0e2a4d8e6 100644 --- a/src/material-experimental/config.bzl +++ b/src/material-experimental/config.bzl @@ -45,6 +45,7 @@ entryPoints = [ "mdc-tabs/testing", "menubar", "popover-edit", + "table", "selection", ] diff --git a/src/material-experimental/table/BUILD.bazel b/src/material-experimental/table/BUILD.bazel new file mode 100644 index 000000000000..78b6c67faffc --- /dev/null +++ b/src/material-experimental/table/BUILD.bazel @@ -0,0 +1,19 @@ +load("//tools:defaults.bzl", "ng_module") + +package(default_visibility = ["//visibility:public"]) + +ng_module( + name = "table", + srcs = glob( + ["**/*.ts"], + exclude = ["**/*.spec.ts"], + ), + module_name = "@angular/material-experimental/table", + deps = [ + "//src/material/table", + "//src/cdk-experimental/table", + "@npm//@angular/common", + "@npm//@angular/core", + "@npm//rxjs", + ], +) diff --git a/src/material-experimental/table/index.ts b/src/material-experimental/table/index.ts new file mode 100644 index 000000000000..676ca90f1ffa --- /dev/null +++ b/src/material-experimental/table/index.ts @@ -0,0 +1,9 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './public-api'; diff --git a/src/material-experimental/table/public-api.ts b/src/material-experimental/table/public-api.ts new file mode 100644 index 000000000000..0f0b4879b5b4 --- /dev/null +++ b/src/material-experimental/table/public-api.ts @@ -0,0 +1,10 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +export * from './scrollable-table-body-layout'; +export * from './scrollable-table-body-module'; diff --git a/src/material-experimental/table/scrollable-table-body-layout.ts b/src/material-experimental/table/scrollable-table-body-layout.ts new file mode 100644 index 000000000000..b7c395d94124 --- /dev/null +++ b/src/material-experimental/table/scrollable-table-body-layout.ts @@ -0,0 +1,23 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {Directive} from '@angular/core'; +import { + _TABLE_LAYOUT_STRATEGY, +} from '@angular/cdk/table/table-layout-strategy'; +import {ScrollableTableBodyLayoutStrategy} from '@angular/cdk-experimental/table'; + +/** A directive that enables scrollable body content for flex tables. */ +@Directive({ + selector: 'mat-table[scrollableBody]', + providers: [ + {provide: _TABLE_LAYOUT_STRATEGY, useClass: ScrollableTableBodyLayoutStrategy}, + ] +}) +export class MatScrollableTableBody { +} diff --git a/src/material-experimental/table/scrollable-table-body-module.ts b/src/material-experimental/table/scrollable-table-body-module.ts new file mode 100644 index 000000000000..5ae89ecaef40 --- /dev/null +++ b/src/material-experimental/table/scrollable-table-body-module.ts @@ -0,0 +1,22 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {NgModule} from '@angular/core'; +import {MatScrollableTableBody} from './scrollable-table-body-layout'; + +export {MatScrollableTableBody}; + +const EXPORTED_DECLARATIONS = [ + MatScrollableTableBody, +]; + +@NgModule({ + exports: EXPORTED_DECLARATIONS, + declarations: EXPORTED_DECLARATIONS, +}) +export class MatScrollableTableBodyModule { }