Skip to content

Commit 81623c4

Browse files
committed
review
1 parent 1117b1c commit 81623c4

18 files changed

+191
-164
lines changed

src/cdk/table/has-sticky-state.ts renamed to src/cdk/table/can-stick.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,14 @@ export type Constructor<T> = new(...args: any[]) => T;
1515
* sticky value.
1616
* @docs-private
1717
*/
18-
export interface HasStickyState {
19-
/** State of whether the sticky input has changed since it was last checked. */
18+
export interface CanStick {
19+
/** Whether the sticky input has changed since it was last checked. */
2020
_hasStickyChanged: boolean;
2121

22-
checkStickyChanged(): boolean;
22+
/** Whether the sticky value has changed since this was last called. */
23+
hasStickyChanged(): boolean;
2324

25+
/** Resets the dirty check for cases where the sticky state has been used without checking. */
2426
resetStickyChanged(): void;
2527
}
2628

@@ -30,13 +32,13 @@ export interface HasStickyState {
3032
* sticky value.
3133
*/
3234
export function mixinHasStickyInput<T extends Constructor<{}>>(base: T):
33-
Constructor<HasStickyState> & T {
35+
Constructor<CanStick> & T {
3436
return class extends base {
35-
/** State of whether the sticky input has changed since it was last checked. */
37+
/** Whether the sticky input has changed since it was last checked. */
3638
_hasStickyChanged: boolean = false;
3739

3840
/** Whether the sticky value has changed since this was last called. */
39-
checkStickyChanged(): boolean {
41+
hasStickyChanged(): boolean {
4042
const hasStickyChanged = this._hasStickyChanged;
4143
this._hasStickyChanged = false;
4244
return hasStickyChanged;

src/cdk/table/cell.ts

+19-19
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {ContentChild, Directive, ElementRef, Input, TemplateRef} from '@angular/core';
1010
import {coerceBooleanProperty} from '@angular/cdk/coercion';
11-
import {HasStickyState, mixinHasStickyInput} from './has-sticky-state';
11+
import {CanStick, mixinHasStickyInput} from './can-stick';
1212

1313
/** Base interface for a cell definition. Captures a column's cell template definition. */
1414
export interface CellDef {
@@ -52,7 +52,7 @@ export const _CdkColumnDefBase = mixinHasStickyInput(CdkColumnDefBase);
5252
* Defines a set of cells available for a table column.
5353
*/
5454
@Directive({selector: '[cdkColumnDef]'})
55-
export class CdkColumnDef extends _CdkColumnDefBase implements HasStickyState {
55+
export class CdkColumnDef extends _CdkColumnDefBase implements CanStick {
5656
/** Unique name for this column. */
5757
@Input('cdkColumnDef')
5858
get name(): string { return this._name; }
@@ -66,25 +66,25 @@ export class CdkColumnDef extends _CdkColumnDefBase implements HasStickyState {
6666
}
6767
_name: string;
6868

69-
/** Whether this column should be sticky positioned on the left of the row */
70-
@Input('stickyLeft')
71-
get stickyLeft(): boolean { return this._stickyLeft; }
72-
set stickyLeft(v: boolean) {
73-
const prevValue = this._stickyLeft;
74-
this._stickyLeft = coerceBooleanProperty(v);
75-
this._hasStickyChanged = prevValue !== this._stickyLeft;
69+
/** Whether this column should be sticky positioned on the start of the row */
70+
@Input('sticky')
71+
get sticky(): boolean { return this._sticky; }
72+
set sticky(v: boolean) {
73+
const prevValue = this._sticky;
74+
this._sticky = coerceBooleanProperty(v);
75+
this._hasStickyChanged = prevValue !== this._sticky;
7676
}
77-
_stickyLeft: boolean = false;
78-
79-
/** Whether this column should be sticky positioned on the right of the right */
80-
@Input('stickyRight')
81-
get stickyRight(): boolean { return this._stickyRight; }
82-
set stickyRight(v: boolean) {
83-
const prevValue = this._stickyRight;
84-
this._stickyRight = coerceBooleanProperty(v);
85-
this._hasStickyChanged = prevValue !== this._stickyRight;
77+
_sticky: boolean = false;
78+
79+
/** Whether this column should be sticky positioned on the end of the row */
80+
@Input('stickyEnd')
81+
get stickyEnd(): boolean { return this._stickyEnd; }
82+
set stickyEnd(v: boolean) {
83+
const prevValue = this._stickyEnd;
84+
this._stickyEnd = coerceBooleanProperty(v);
85+
this._hasStickyChanged = prevValue !== this._stickyEnd;
8686
}
87-
_stickyRight: boolean = false;
87+
_stickyEnd: boolean = false;
8888

8989
/** @docs-private */
9090
@ContentChild(CdkCellDef) cell: CdkCellDef;

src/cdk/table/public-api.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export * from './cell';
1111
export * from './row';
1212
export * from './table-module';
1313
export * from './sticky-styler';
14-
export * from './has-sticky-state';
14+
export * from './can-stick';
1515

1616
/** Re-export DataSource for a more intuitive experience for users of just the table. */
1717
export {DataSource} from '@angular/cdk/collections';

src/cdk/table/row.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
} from '@angular/core';
2222
import {CdkCellDef, CdkColumnDef} from './cell';
2323
import {coerceBooleanProperty} from '@angular/cdk/coercion';
24-
import {HasStickyState, mixinHasStickyInput} from './has-sticky-state';
24+
import {CanStick, mixinHasStickyInput} from './can-stick';
2525

2626
/**
2727
* The row template that can be used by the mat-table. Should not be used outside of the
@@ -86,13 +86,13 @@ export const _CdkHeaderRowDefBase = mixinHasStickyInput(CdkHeaderRowDefBase);
8686
selector: '[cdkHeaderRowDef]',
8787
inputs: ['columns: cdkHeaderRowDef', 'sticky: cdkHeaderRowDefSticky'],
8888
})
89-
export class CdkHeaderRowDef extends _CdkHeaderRowDefBase implements HasStickyState {
89+
export class CdkHeaderRowDef extends _CdkHeaderRowDefBase implements CanStick {
90+
get sticky(): boolean { return this._sticky; }
9091
set sticky(v: boolean) {
9192
const prevValue = this._sticky;
9293
this._sticky = coerceBooleanProperty(v);
9394
this._hasStickyChanged = prevValue !== this._sticky;
9495
}
95-
get sticky(): boolean { return this._sticky; }
9696
_sticky: boolean;
9797

9898
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {
@@ -113,13 +113,13 @@ export const _CdkFooterRowDefBase = mixinHasStickyInput(CdkFooterRowDefBase);
113113
selector: '[cdkFooterRowDef]',
114114
inputs: ['columns: cdkFooterRowDef', 'sticky: cdkFooterRowDefSticky'],
115115
})
116-
export class CdkFooterRowDef extends _CdkFooterRowDefBase implements HasStickyState {
116+
export class CdkFooterRowDef extends _CdkFooterRowDefBase implements CanStick {
117+
get sticky(): boolean { return this._sticky; }
117118
set sticky(v: boolean) {
118119
const prevValue = this._sticky;
119120
this._sticky = coerceBooleanProperty(v);
120121
this._hasStickyChanged = prevValue !== this._sticky;
121122
}
122-
get sticky(): boolean { return this._sticky; }
123123
_sticky: boolean;
124124

125125
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {

src/cdk/table/sticky-styler.ts

+78-53
Original file line numberDiff line numberDiff line change
@@ -6,88 +6,107 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
/** Directions that can be used when setting sticky positioning. */
9+
/**
10+
* Directions that can be used when setting sticky positioning.
11+
* @docs-private
12+
*/
1013
export type StickyDirection = 'top' | 'bottom' | 'left' | 'right';
1114

1215
/**
13-
* Z-index values that should be used when sticking row cells to the top and bottom. If one of those
14-
* cells are also stuck to the left or right, their z-index should be incremented by one. In doing
15-
* this, it is guaranteed that header cells will always cover footer cells, and both will always
16-
* cover data rows.
16+
* List of all possible directions that can be used for sticky positioning.
17+
* @docs-private
1718
*/
18-
export enum StickyRowZIndex {
19-
Top = 100,
20-
Bottom = 10,
21-
Left = 1,
22-
Right = 1,
23-
}
19+
export const STICKY_DIRECTIONS: StickyDirection[] = ['top', 'bottom', 'left', 'right'];
2420

25-
/** Applies and removes sticky positioning styles to the `CdkTable` rows and columns cells. */
21+
/**
22+
* Applies and removes sticky positioning styles to the `CdkTable` rows and columns cells.
23+
* @docs-private
24+
*/
2625
export class StickyStyler {
27-
constructor(private usesNativeHtmlTable: boolean, private stickyCellCSS: string) { }
26+
/**
27+
* @param isNativeHtmlTable Whether the sticky logic should be based on a table
28+
* that uses the native `<table>` element.
29+
* @param stickCellCSS The CSS class that will be applied to every row/cell that has
30+
* sticky positioning applied.
31+
*/
32+
constructor(private isNativeHtmlTable: boolean, private stickCellCSS: string) { }
2833

2934
/**
3035
* Clears the sticky positioning styles from the row and its cells by resetting the `position`
3136
* style, setting the zIndex to 0, and unsetting each provided sticky direction.
37+
* @param rows The list of rows that should be cleared from sticking in the provided directions
38+
* @param stickyDirections The directions that should no longer be set as sticky on the rows.
3239
*/
33-
clearStickyPositioningStyles(rows: HTMLElement[], stickyDirections: StickyDirection[]) {
34-
rows.forEach(row => {
40+
clearStickyPositioning(rows: HTMLElement[], stickyDirections: StickyDirection[]) {
41+
for (let row of rows) {
3542
this._removeStickyStyle(row, stickyDirections);
3643
for (let i = 0; i < row.children.length; i++) {
3744
const cell = row.children[i] as HTMLElement;
3845
this._removeStickyStyle(cell, stickyDirections);
3946
}
40-
});
47+
}
4148
}
4249

4350
/**
4451
* Applies sticky left and right positions to the cells of each row according to the sticky
4552
* states of the rendered column definitions.
53+
* @param rows The rows that should have its set of cells stuck according to the sticky states.
54+
* @param stickyStartStates A list of boolean states where each state represents whether the cell
55+
* in this index position should be stuck to the start of the row.
56+
* @param stickyEndStates A list of boolean states where each state represents whether the cell
57+
* in this index position should be stuck to the end of the row.
4658
*/
4759
updateStickyColumns(
48-
rows: HTMLElement[], stickyLeftStates: boolean[], stickyRightStates: boolean[]) {
60+
rows: HTMLElement[], stickyStartStates: boolean[], stickyEndStates: boolean[]) {
4961
const hasStickyColumns =
50-
stickyLeftStates.some(state => state) || stickyRightStates.some(state => state);
62+
stickyStartStates.some(state => state) || stickyEndStates.some(state => state);
5163
if (!rows.length || !hasStickyColumns) {
5264
return;
5365
}
5466

67+
const numCells = rows[0].children.length;
5568
const cellWidths: number[] = this._getCellWidths(rows[0]);
56-
const leftPositions = this._getStickyLeftColumnPositions(cellWidths, stickyLeftStates);
57-
const rightPositions = this._getStickyRightColumnPositions(cellWidths, stickyRightStates);
69+
const startPositions = this._getStickyStartColumnPositions(cellWidths, stickyStartStates);
70+
const endPositions = this._getStickyEndColumnPositions(cellWidths, stickyEndStates);
5871

59-
rows.forEach(row => {
60-
for (let i = 0; i < row.children.length; i++) {
72+
for (let row of rows) {
73+
for (let i = 0; i < numCells; i++) {
6174
const cell = row.children[i] as HTMLElement;
62-
if (stickyLeftStates[i]) {
63-
this._addStickyStyle(cell, 'left', leftPositions[i]);
75+
if (stickyStartStates[i]) {
76+
this._addStickyStyle(cell, 'left', startPositions[i]);
6477
}
6578

66-
if (stickyRightStates[i]) {
67-
this._addStickyStyle(cell, 'right', rightPositions[i]);
79+
if (stickyEndStates[i]) {
80+
this._addStickyStyle(cell, 'right', endPositions[i]);
6881
}
6982
}
70-
});
83+
}
7184
}
7285

7386
/**
7487
* Applies sticky positioning to the row's cells if using the native table layout, and to the
7588
* row itself otherwise.
89+
* @param rowsToStick The list of rows that should be stuck according to their corresponding
90+
* sticky state and to the provided top or bottom position.
91+
* @param stickyStates A list of boolean states where each state represents whether the row
92+
* should be stuck in the particular top or bottom position.
93+
* @param position The position direction in which the row should be stuck if that row should be
94+
* sticky.
95+
*
7696
*/
77-
stickRows(rows: HTMLElement[], stickyStates: boolean[], position: 'top' | 'bottom') {
78-
// Bottom-positions rows should stick in reverse order
79-
// (e.g. last stuck item will be bottom: 0px)
80-
if (position === 'bottom') {
81-
rows = rows.reverse();
82-
}
97+
stickRows(rowsToStick: HTMLElement[], stickyStates: boolean[], position: 'top' | 'bottom') {
98+
// If positioning the rows to the bottom, reverse their order when evaluating the sticky
99+
// position such that the last row stuck will be "bottom: 0px" and so on.
100+
const rows = position === 'bottom' ? rowsToStick.reverse() : rowsToStick;
83101

84102
let stickyHeight = 0;
85-
rows.forEach((row, i) => {
86-
if (!stickyStates[i]) {
87-
return;
103+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
104+
if (!stickyStates[rowIndex]) {
105+
continue;
88106
}
89107

90-
if (this.usesNativeHtmlTable) {
108+
const row = rows[rowIndex];
109+
if (this.isNativeHtmlTable) {
91110
for (let j = 0; j < row.children.length; j++) {
92111
const cell = row.children[j] as HTMLElement;
93112
this._addStickyStyle(cell, position, stickyHeight);
@@ -99,7 +118,7 @@ export class StickyStyler {
99118
}
100119

101120
stickyHeight += row.getBoundingClientRect().height;
102-
});
121+
}
103122
}
104123

105124
/**
@@ -109,7 +128,7 @@ export class StickyStyler {
109128
* the tfoot element.
110129
*/
111130
updateStickyFooterContainer(tableElement: Element, stickyStates: boolean[]) {
112-
if (!this.usesNativeHtmlTable) {
131+
if (!this.isNativeHtmlTable) {
113132
return;
114133
}
115134

@@ -127,15 +146,17 @@ export class StickyStyler {
127146
* sticky position if there are no more directions.
128147
*/
129148
_removeStickyStyle(element: HTMLElement, stickyDirections: StickyDirection[]) {
130-
stickyDirections.forEach(dir => element.style[dir] = '');
149+
for (let dir of stickyDirections) {
150+
element.style[dir] = '';
151+
}
131152
element.style.zIndex = this._getCalculatedZIndex(element);
132153

133154
// If the element no longer has any more sticky directions, remove sticky positioning and
134155
// the sticky CSS class.
135-
const hasDirection = ['top', 'bottom', 'left', 'right'].some(dir => element.style[dir]);
156+
const hasDirection = STICKY_DIRECTIONS.some(dir => !!element.style[dir]);
136157
if (!hasDirection) {
137158
element.style.position = '';
138-
element.classList.remove(this.stickyCellCSS);
159+
element.classList.remove(this.stickCellCSS);
139160
}
140161
}
141162

@@ -145,18 +166,22 @@ export class StickyStyler {
145166
* direction and value.
146167
*/
147168
_addStickyStyle(element: HTMLElement, dir: StickyDirection, dirValue: number) {
148-
element.classList.add(this.stickyCellCSS);
169+
element.classList.add(this.stickCellCSS);
149170
element.style[dir] = `${dirValue}px`;
150171
element.style.cssText += 'position: -webkit-sticky; position: sticky; ';
151172
element.style.zIndex = this._getCalculatedZIndex(element);
152173
}
153174

154175
/**
155-
* Calculate what the z-index should be for the element depending on the sticky directions styles
156-
* that it has set. It should be the case that elements with a top direction should always be at
157-
* the forefront, followed by bottom direction elements. Finally, anything with left or right
158-
* direction should come behind those. All else should be the lowest and not have any increased
159-
* z-index.
176+
* Calculate what the z-index should be for the element, depending on what directions (top,
177+
* bottom, left, right) have been set. It should be true that elements with a top direction
178+
* should have the highest index since these are elements like a table header. If any of those
179+
* elements are also sticky in another direction, then they should appear above other elements
180+
* that are only sticky top (e.g. a sticky column on a sticky header). Bottom-sticky elements
181+
* (e.g. footer rows) should then be next in the ordering such that they are below the header
182+
* but above any non-sticky elements. Finally, left/right sticky elements (e.g. sticky columns)
183+
* should minimally increment so that they are above non-sticky elements but below top and bottom
184+
* elements.
160185
*/
161186
_getCalculatedZIndex(element: HTMLElement): string {
162187
const zIndexIncrements = {
@@ -167,13 +192,13 @@ export class StickyStyler {
167192
};
168193

169194
let zIndex = 0;
170-
['top', 'bottom', 'left', 'right'].forEach(dir => {
195+
for (let dir of STICKY_DIRECTIONS) {
171196
if (element.style[dir]) {
172197
zIndex += zIndexIncrements[dir];
173198
}
174-
});
199+
}
175200

176-
return String(zIndex);
201+
return `${zIndex}`;
177202
}
178203

179204
/** Gets the widths for each cell in the provided row. */
@@ -193,7 +218,7 @@ export class StickyStyler {
193218
* accumulation of all sticky column cell widths to the left and right, respectively.
194219
* Non-sticky cells do not need to have a value set since their positions will not be applied.
195220
*/
196-
_getStickyLeftColumnPositions(widths: number[], stickyStates: boolean[]): number[] {
221+
_getStickyStartColumnPositions(widths: number[], stickyStates: boolean[]): number[] {
197222
const positions: number[] = [];
198223
let nextPosition = 0;
199224

@@ -212,7 +237,7 @@ export class StickyStyler {
212237
* accumulation of all sticky column cell widths to the left and right, respectively.
213238
* Non-sticky cells do not need to have a value set since their positions will not be applied.
214239
*/
215-
_getStickyRightColumnPositions(widths: number[], stickyStates: boolean[]): number[] {
240+
_getStickyEndColumnPositions(widths: number[], stickyStates: boolean[]): number[] {
216241
const positions: number[] = [];
217242
let nextPosition = 0;
218243

0 commit comments

Comments
 (0)