Skip to content

Commit 8b4dc94

Browse files
committed
support rtl sticky columns
1 parent c3dd30f commit 8b4dc94

File tree

7 files changed

+78
-13
lines changed

7 files changed

+78
-13
lines changed

src/cdk/table/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ ng_module(
88
srcs = glob(["**/*.ts"], exclude=["**/*.spec.ts"]),
99
module_name = "@angular/cdk/table",
1010
deps = [
11+
"//src/cdk/bidi",
1112
"//src/cdk/collections",
1213
"//src/cdk/coercion",
1314
"@rxjs",

src/cdk/table/sticky-styler.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
* Directions that can be used when setting sticky positioning.
1111
* @docs-private
1212
*/
13+
import {Direction} from '@angular/cdk/bidi';
14+
1315
export type StickyDirection = 'top' | 'bottom' | 'left' | 'right';
1416

1517
/**
@@ -28,8 +30,12 @@ export class StickyStyler {
2830
* that uses the native `<table>` element.
2931
* @param stickCellCSS The CSS class that will be applied to every row/cell that has
3032
* sticky positioning applied.
33+
* @param direction The directionality context of the table (ltr/rtl); affects column positioning
34+
* by reversing left/right positions.
3135
*/
32-
constructor(private isNativeHtmlTable: boolean, private stickCellCSS: string) { }
36+
constructor(private isNativeHtmlTable: boolean,
37+
private stickCellCSS: string,
38+
public direction: Direction) { }
3339

3440
/**
3541
* Clears the sticky positioning styles from the row and its cells by resetting the `position`
@@ -68,16 +74,17 @@ export class StickyStyler {
6874
const cellWidths: number[] = this._getCellWidths(rows[0]);
6975
const startPositions = this._getStickyStartColumnPositions(cellWidths, stickyStartStates);
7076
const endPositions = this._getStickyEndColumnPositions(cellWidths, stickyEndStates);
77+
const isLtr = this.direction === 'ltr';
7178

7279
for (let row of rows) {
7380
for (let i = 0; i < numCells; i++) {
7481
const cell = row.children[i] as HTMLElement;
7582
if (stickyStartStates[i]) {
76-
this._addStickyStyle(cell, 'left', startPositions[i]);
83+
this._addStickyStyle(cell, isLtr ? 'left' : 'right', startPositions[i]);
7784
}
7885

7986
if (stickyEndStates[i]) {
80-
this._addStickyStyle(cell, 'right', endPositions[i]);
87+
this._addStickyStyle(cell, isLtr ? 'right' : 'left', endPositions[i]);
8188
}
8289
}
8390
}

src/cdk/table/table.spec.ts

+34-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
getTableUnknownColumnError,
2525
getTableUnknownDataSourceError
2626
} from './table-errors';
27+
import {BidiModule} from '@angular/cdk/bidi';
2728

2829
describe('CdkTable', () => {
2930
let fixture: ComponentFixture<any>;
@@ -33,7 +34,7 @@ describe('CdkTable', () => {
3334
function createComponent<T>(componentType: Type<T>, declarations: any[] = []):
3435
ComponentFixture<T> {
3536
TestBed.configureTestingModule({
36-
imports: [CdkTableModule],
37+
imports: [CdkTableModule, BidiModule],
3738
declarations: [componentType, ...declarations],
3839
}).compileComponents();
3940

@@ -831,6 +832,36 @@ describe('CdkTable', () => {
831832
footerRows.forEach(row => expectNoStickyStyles(getFooterCells(row)));
832833
});
833834

835+
it('should reverse directions for sticky columns in rtl', () => {
836+
component.dir = 'rtl';
837+
component.stickyStartColumns = ['column-1', 'column-2'];
838+
component.stickyEndColumns = ['column-5', 'column-6'];
839+
fixture.detectChanges();
840+
841+
const firstColumnWidth = getHeaderCells(headerRows[0])[0].getBoundingClientRect().width;
842+
const lastColumnWidth = getHeaderCells(headerRows[0])[5].getBoundingClientRect().width;
843+
844+
let headerCells = getHeaderCells(headerRows[0]);
845+
expectStickyStyles(headerCells[0], '1', {right: '0px'});
846+
expectStickyStyles(headerCells[1], '1', {right: `${firstColumnWidth}px`});
847+
expectStickyStyles(headerCells[4], '1', {left: `${lastColumnWidth}px`});
848+
expectStickyStyles(headerCells[5], '1', {left: '0px'});
849+
850+
dataRows.forEach(row => {
851+
let cells = getCells(row);
852+
expectStickyStyles(cells[0], '1', {right: '0px'});
853+
expectStickyStyles(cells[1], '1', {right: `${firstColumnWidth}px`});
854+
expectStickyStyles(cells[4], '1', {left: `${lastColumnWidth}px`});
855+
expectStickyStyles(cells[5], '1', {left: '0px'});
856+
});
857+
858+
let footerCells = getFooterCells(footerRows[0]);
859+
expectStickyStyles(footerCells[0], '1', {right: '0px'});
860+
expectStickyStyles(footerCells[1], '1', {right: `${firstColumnWidth}px`});
861+
expectStickyStyles(footerCells[4], '1', {left: `${lastColumnWidth}px`});
862+
expectStickyStyles(footerCells[5], '1', {left: '0px'});
863+
});
864+
834865
it('should stick and unstick combination of sticky header, footer, and columns', () => {
835866
component.stickyHeaders = ['header-1'];
836867
component.stickyFooters = ['footer-3'];
@@ -1710,7 +1741,7 @@ class TrackByCdkTableApp {
17101741

17111742
@Component({
17121743
template: `
1713-
<cdk-table [dataSource]="dataSource">
1744+
<cdk-table [dataSource]="dataSource" [dir]="dir">
17141745
<ng-container [cdkColumnDef]="column" *ngFor="let column of columns"
17151746
[sticky]="isStuck(stickyStartColumns, column)"
17161747
[stickyEnd]="isStuck(stickyEndColumns, column)">
@@ -1749,6 +1780,7 @@ class StickyFlexLayoutCdkTableApp {
17491780

17501781
@ViewChild(CdkTable) table: CdkTable<TestData>;
17511782

1783+
dir = 'ltr';
17521784
stickyHeaders: string[] = [];
17531785
stickyFooters: string[] = [];
17541786
stickyStartColumns: string[] = [];

src/cdk/table/table.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
IterableDiffers,
2525
OnDestroy,
2626
OnInit,
27+
Optional,
2728
QueryList,
2829
TemplateRef,
2930
TrackByFunction,
@@ -53,6 +54,7 @@ import {
5354
} from './table-errors';
5455
import {coerceBooleanProperty} from '@angular/cdk/coercion';
5556
import {StickyStyler} from './sticky-styler';
57+
import {Direction, Directionality} from '@angular/cdk/bidi';
5658

5759
/** Interface used to provide an outlet for rows to be inserted into. */
5860
export interface RowOutlet {
@@ -356,7 +358,8 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
356358
constructor(protected readonly _differs: IterableDiffers,
357359
protected readonly _changeDetectorRef: ChangeDetectorRef,
358360
protected readonly _elementRef: ElementRef,
359-
@Attribute('role') role: string) {
361+
@Attribute('role') role: string,
362+
@Optional() protected readonly _dir: Directionality) {
360363
if (!role) {
361364
this._elementRef.nativeElement.setAttribute('role', 'grid');
362365
}
@@ -365,7 +368,7 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
365368
}
366369

367370
ngOnInit() {
368-
this._stickyStyler = new StickyStyler(this._isNativeHtmlTable, this.stickyCssClass);
371+
this._setupStickyStyler();
369372

370373
if (this._isNativeHtmlTable) {
371374
this._applyNativeTableSections();
@@ -988,6 +991,22 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
988991
this.updateStickyColumnStyles();
989992
}
990993
}
994+
995+
/**
996+
* Creates the sticky styler that will be used for sticky rows and columns. Listens
997+
* for directionality changes and provides the latest direction to the styler. Re-applies column
998+
* stickiness when directionality changes.
999+
*/
1000+
private _setupStickyStyler() {
1001+
const direction: Direction = this._dir ? this._dir.value : 'ltr';
1002+
this._stickyStyler = new StickyStyler(this._isNativeHtmlTable, this.stickyCssClass, direction);
1003+
(this._dir ? this._dir.change : observableOf<Direction>())
1004+
.pipe(takeUntil(this._onDestroy))
1005+
.subscribe(value => {
1006+
this._stickyStyler.direction = value;
1007+
this.updateStickyColumnStyles();
1008+
});
1009+
}
9911010
}
9921011

9931012
/** Utility function that gets a merged list of the entries in a QueryList and values of a Set. */

src/demo-app/example/example.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import {coerceBooleanProperty} from '@angular/cdk/coercion';
10-
import {Component, ElementRef, Input, OnInit} from '@angular/core';
10+
import {Component, ElementRef, Injector, Input, OnInit} from '@angular/core';
1111
import {EXAMPLE_COMPONENTS} from '@angular/material-examples';
1212

1313
@Component({
@@ -54,11 +54,13 @@ export class Example implements OnInit {
5454

5555
title: string;
5656

57-
constructor(private elementRef: ElementRef) { }
57+
constructor(private elementRef: ElementRef, private injector: Injector) { }
5858

5959
ngOnInit() {
60-
const element = document.createElement(this.id);
61-
this.elementRef.nativeElement.appendChild(element);
60+
// Should be created with this component's injector to capture the whole injector which may
61+
// include provided things like Directionality.
62+
const exampleElementCtor = customElements.get(this.id);
63+
this.elementRef.nativeElement.appendChild(new exampleElementCtor(this.injector));
6264

6365
this.title = EXAMPLE_COMPONENTS[this.id] ? EXAMPLE_COMPONENTS[this.id].title : '';
6466
}

src/lib/table/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ ng_module(
99
module_name = "@angular/material/table",
1010
assets = [":table_css"],
1111
deps = [
12+
"//src/cdk/bidi",
1213
"//src/lib/core",
1314
"//src/lib/paginator",
1415
"//src/lib/sort",

src/lib/table/table.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ import {
1313
Component,
1414
ElementRef,
1515
IterableDiffers,
16+
Optional,
1617
ViewEncapsulation
1718
} from '@angular/core';
1819
import {CDK_TABLE_TEMPLATE, CdkTable} from '@angular/cdk/table';
20+
import {Directionality} from '@angular/cdk/bidi';
1921

2022
/**
2123
* Wrapper for the CdkTable with Material design styles.
@@ -44,7 +46,8 @@ export class MatTable<T> extends CdkTable<T> {
4446
constructor(protected _differs: IterableDiffers,
4547
protected _changeDetectorRef: ChangeDetectorRef,
4648
protected _elementRef: ElementRef,
47-
@Attribute('role') role: string) {
48-
super(_differs, _changeDetectorRef, _elementRef, role);
49+
@Attribute('role') role: string,
50+
@Optional() protected readonly _dir: Directionality) {
51+
super(_differs, _changeDetectorRef, _elementRef, role, _dir);
4952
}
5053
}

0 commit comments

Comments
 (0)