Skip to content

Commit

Permalink
fix(column-resize): Resize table as well as columns, improve handing … (
Browse files Browse the repository at this point in the history
  • Loading branch information
kseamon authored and mmalerba committed May 15, 2020
1 parent 07711b4 commit 87b8edb
Show file tree
Hide file tree
Showing 23 changed files with 191 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {FLEX_PROVIDERS} from './constants';
export class CdkColumnResizeFlex extends ColumnResize {
constructor(
readonly columnResizeNotifier: ColumnResizeNotifier,
protected readonly elementRef: ElementRef,
readonly elementRef: ElementRef<HTMLElement>,
protected readonly eventDispatcher: HeaderRowEventDispatcher,
protected readonly ngZone: NgZone,
protected readonly notifier: ColumnResizeNotifierSource) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {TABLE_PROVIDERS} from './constants';
export class CdkColumnResize extends ColumnResize {
constructor(
readonly columnResizeNotifier: ColumnResizeNotifier,
protected readonly elementRef: ElementRef,
readonly elementRef: ElementRef<HTMLElement>,
protected readonly eventDispatcher: HeaderRowEventDispatcher,
protected readonly ngZone: NgZone,
protected readonly notifier: ColumnResizeNotifierSource) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {FLEX_PROVIDERS} from './constants';
export class CdkDefaultEnabledColumnResizeFlex extends ColumnResize {
constructor(
readonly columnResizeNotifier: ColumnResizeNotifier,
protected readonly elementRef: ElementRef,
readonly elementRef: ElementRef<HTMLElement>,
protected readonly eventDispatcher: HeaderRowEventDispatcher,
protected readonly ngZone: NgZone,
protected readonly notifier: ColumnResizeNotifierSource) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {TABLE_PROVIDERS} from './constants';
export class CdkDefaultEnabledColumnResize extends ColumnResize {
constructor(
readonly columnResizeNotifier: ColumnResizeNotifier,
protected readonly elementRef: ElementRef,
readonly elementRef: ElementRef<HTMLElement>,
protected readonly eventDispatcher: HeaderRowEventDispatcher,
protected readonly ngZone: NgZone,
protected readonly notifier: ColumnResizeNotifierSource) {
Expand Down
8 changes: 7 additions & 1 deletion src/cdk-experimental/column-resize/column-resize-notifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export interface ColumnSize {

/** The width in pixels of the column. */
readonly size: number;

/** The width in pixels of the column prior to this update, if known. */
readonly previousSize?: number;
}

/** Interface describing column size changes. */
Expand All @@ -28,7 +31,10 @@ export interface ColumnSizeAction extends ColumnSize {
readonly completeImmediately?: boolean;
}

/** Originating source of column resize events within a table. */
/**
* Originating source of column resize events within a table.
* @docs-private
*/
@Injectable()
export class ColumnResizeNotifierSource {
/** Emits when an in-progress resize is canceled. */
Expand Down
11 changes: 9 additions & 2 deletions src/cdk-experimental/column-resize/column-resize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ export abstract class ColumnResize implements AfterViewInit, OnDestroy {
/* Publicly accessible interface for triggering and being notified of resizes. */
abstract readonly columnResizeNotifier: ColumnResizeNotifier;

protected abstract readonly elementRef: ElementRef<HTMLElement>;
/* ElementRef that this directive is attached to. Exposed For use by column-level directives */
abstract readonly elementRef: ElementRef<HTMLElement>;

protected abstract readonly eventDispatcher: HeaderRowEventDispatcher;
protected abstract readonly ngZone: NgZone;
protected abstract readonly notifier: ColumnResizeNotifierSource;
Expand Down Expand Up @@ -61,6 +63,11 @@ export abstract class ColumnResize implements AfterViewInit, OnDestroy {
return `cdk-column-resize-${this.selectorId}`;
}

/** Called when a column in the table is resized. Applies a css class to the table element. */
setResized() {
this.elementRef.nativeElement!.classList.add(WITH_RESIZED_COLUMN_CLASS);
}

private _listenForRowHoverEvents() {
this.ngZone.runOutsideAngular(() => {
const element = this.elementRef.nativeElement!;
Expand All @@ -87,7 +94,7 @@ export abstract class ColumnResize implements AfterViewInit, OnDestroy {
takeUntil(this.destroyed),
take(1),
).subscribe(() => {
this.elementRef.nativeElement!.classList.add(WITH_RESIZED_COLUMN_CLASS);
this.setResized();
});
}

Expand Down
27 changes: 6 additions & 21 deletions src/cdk-experimental/column-resize/overlay-handle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,8 @@ export abstract class ResizeOverlayHandle implements AfterViewInit, OnDestroy {

const startX = mousedownEvent.screenX;

const initialOverlayOffset = this._getOverlayOffset();
const initialSize = this._getOriginWidth();
let overlayOffset = initialOverlayOffset;
let overlayOffset = this._getOverlayOffset();
let originOffset = this._getOriginOffset();
let size = initialSize;
let overshot = 0;
Expand Down Expand Up @@ -143,7 +142,7 @@ export abstract class ResizeOverlayHandle implements AfterViewInit, OnDestroy {
Math.max(computedNewSize, this.resizeRef.minWidthPx, 0), this.resizeRef.maxWidthPx);

this.resizeNotifier.triggerResize.next(
{columnId: this.columnDef.name, size: computedNewSize});
{columnId: this.columnDef.name, size: computedNewSize, previousSize: size});

const originNewSize = this._getOriginWidth();
const originNewOffset = this._getOriginOffset();
Expand All @@ -153,7 +152,7 @@ export abstract class ResizeOverlayHandle implements AfterViewInit, OnDestroy {
originOffset = originNewOffset;

overshot += deltaX + (this._isLtr() ? -originSizeDeltaX : originSizeDeltaX);
overlayOffset += originSizeDeltaX + originOffsetDeltaX;
overlayOffset += originOffsetDeltaX + (this._isLtr() ? originSizeDeltaX : 0);

this._updateOverlayOffset(overlayOffset);
});
Expand All @@ -169,29 +168,15 @@ export abstract class ResizeOverlayHandle implements AfterViewInit, OnDestroy {
}

private _getOriginOffset(): number {
const originElement = this.resizeRef.origin.nativeElement!;
const offsetLeft = originElement.offsetLeft;

return this._isLtr() ?
offsetLeft :
originElement.offsetParent!.offsetWidth - (offsetLeft + this._getOriginWidth());
return this.resizeRef.origin.nativeElement!.offsetLeft;
}

private _getOverlayOffset(): number {
const overlayElement = this.resizeRef.overlayRef.overlayElement;
return this._isLtr() ?
parseInt(overlayElement.style.left!, 10) : parseInt(overlayElement.style.right!, 10);
return parseInt(this.resizeRef.overlayRef.overlayElement.style.left!, 10);
}

private _updateOverlayOffset(offset: number): void {
const overlayElement = this.resizeRef.overlayRef.overlayElement;
const overlayOffsetCssValue = coerceCssPixelValue(offset);

if (this._isLtr()) {
overlayElement.style.left = overlayOffsetCssValue;
} else {
overlayElement.style.right = overlayOffsetCssValue;
}
this.resizeRef.overlayRef.overlayElement.style.left = coerceCssPixelValue(offset);
}

private _isLtr(): boolean {
Expand Down
17 changes: 11 additions & 6 deletions src/cdk-experimental/column-resize/resizable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ export abstract class Resizable<HandleComponent extends ResizeOverlayHandle>
this.minWidthPxInternal = value;

if (this.elementRef.nativeElement) {
this.columnResize.setResized();
this._applyMinWidthPx();
}
}
Expand All @@ -82,6 +83,7 @@ export abstract class Resizable<HandleComponent extends ResizeOverlayHandle>
this.maxWidthPxInternal = value;

if (this.elementRef.nativeElement) {
this.columnResize.setResized();
this._applyMaxWidthPx();
}
}
Expand Down Expand Up @@ -116,20 +118,23 @@ export abstract class Resizable<HandleComponent extends ResizeOverlayHandle>
// of two table cells and is also useful for displaying a resize thumb
// over both cells and extending it down the table as needed.

const isRtl = this.directionality.value === 'rtl';
const positionStrategy = this.overlay.position()
.flexibleConnectedTo(this.elementRef.nativeElement!)
.withFlexibleDimensions(false)
.withGrowAfterOpen(false)
.withPush(false)
.withDefaultOffsetX(isRtl ? 1 : 0)
.withPositions([{
originX: 'end',
originX: isRtl ? 'start' : 'end',
originY: 'top',
overlayX: 'center',
overlayY: 'top',
}]);

return this.overlay.create({
direction: this.directionality,
// Always position the overlay based on left-indexed coordinates.
direction: 'ltr',
disposeOnNavigation: true,
positionStrategy,
scrollStrategy: this.overlay.scrollStrategies.reposition(),
Expand Down Expand Up @@ -166,9 +171,9 @@ export abstract class Resizable<HandleComponent extends ResizeOverlayHandle>
).pipe(
takeUntilDestroyed,
filter(columnSize => columnSize.columnId === this.columnDef.name),
).subscribe(({size, completeImmediately}) => {
).subscribe(({size, previousSize, completeImmediately}) => {
this.elementRef.nativeElement!.classList.add(OVERLAY_ACTIVE_CLASS);
this._applySize(size);
this._applySize(size, previousSize);

if (completeImmediately) {
this._completeResizeOperation();
Expand Down Expand Up @@ -223,11 +228,11 @@ export abstract class Resizable<HandleComponent extends ResizeOverlayHandle>
this.overlayRef!.updateSize({height: this.elementRef.nativeElement!.offsetHeight});
}

private _applySize(sizeInPixels: number): void {
private _applySize(sizeInPixels: number, previousSize?: number): void {
const sizeToApply = Math.min(Math.max(sizeInPixels, this.minWidthPx, 0), this.maxWidthPx);

this.resizeStrategy.applyColumnSize(this.columnDef.cssClassFriendlyName,
this.elementRef.nativeElement!, sizeToApply);
this.elementRef.nativeElement!, sizeToApply, previousSize);
}

private _applyMinWidthPx(): void {
Expand Down
84 changes: 74 additions & 10 deletions src/cdk-experimental/column-resize/resize-strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,34 @@ import {ColumnResize} from './column-resize';
*/
@Injectable()
export abstract class ResizeStrategy {
protected abstract readonly columnResize: ColumnResize;

/** Updates the width of the specified column. */
abstract applyColumnSize(
cssFriendlyColumnName: string,
columnHeader: HTMLElement,
sizeInPx: number): void;
sizeInPx: number,
previousSizeInPx?: number): void;

/** Applies a minimum width to the specified column, updating its current width as needed. */
abstract applyMinColumnSize(
cssFriendlyColumnName: string,
columnHeader: HTMLElement,
minSizeInPx: number): void;

/** Applies a maximum width to the specified column, updating its current width as needed. */
abstract applyMaxColumnSize(
cssFriendlyColumnName: string,
columnHeader: HTMLElement,
minSizeInPx: number): void;

/** Adjusts the width of the table element by the specified delta. */
protected updateTableWidth(delta: number): void {
const table = this.columnResize.elementRef.nativeElement;
const tableWidth = getElementWidth(table);

table.style.width = coerceCssPixelValue(tableWidth + delta);
}
}

/**
Expand All @@ -43,17 +57,31 @@ export abstract class ResizeStrategy {
*/
@Injectable()
export class TableLayoutFixedResizeStrategy extends ResizeStrategy {
applyColumnSize(_: string, columnHeader: HTMLElement, sizeInPx: number): void {
constructor(protected readonly columnResize: ColumnResize) {
super();
}

applyColumnSize(_: string, columnHeader: HTMLElement, sizeInPx: number,
previousSizeInPx?: number): void {
const delta = sizeInPx - (previousSizeInPx ?? getElementWidth(columnHeader));

columnHeader.style.width = coerceCssPixelValue(sizeInPx);

this.updateTableWidth(delta);
}

applyMinColumnSize(_: string, columnHeader: HTMLElement, sizeInPx: number): void {
columnHeader.style.minWidth = coerceCssPixelValue(sizeInPx);
const currentWidth = getElementWidth(columnHeader);
const newWidth = Math.max(currentWidth, sizeInPx);

this.applyColumnSize(_, columnHeader, newWidth, currentWidth);
}

applyMaxColumnSize(): void {
// Intentionally omitted as max-width causes strange rendering issues in Chrome.
// Max size will still apply when the user is resizing this column.
applyMaxColumnSize(_: string, columnHeader: HTMLElement, sizeInPx: number): void {
const currentWidth = getElementWidth(columnHeader);
const newWidth = Math.min(currentWidth, sizeInPx);

this.applyColumnSize(_, columnHeader, newWidth, currentWidth);
}
}

Expand All @@ -76,16 +104,23 @@ export class CdkFlexTableResizeStrategy extends ResizeStrategy implements OnDest
protected readonly defaultMaxSize = Number.MAX_SAFE_INTEGER;

constructor(
private readonly _columnResize: ColumnResize,
protected readonly columnResize: ColumnResize,
@Inject(DOCUMENT) document: any) {
super();
this._document = document;
}

applyColumnSize(cssFriendlyColumnName: string, _: HTMLElement, sizeInPx: number): void {
applyColumnSize(cssFriendlyColumnName: string, columnHeader: HTMLElement,
sizeInPx: number, previousSizeInPx?: number): void {
// Optimization: Check applied width first as we probably set it already before reading
// offsetWidth which triggers layout.
const delta = sizeInPx - (previousSizeInPx ??
(this._getAppliedWidth(cssFriendlyColumnName) || columnHeader.offsetWidth));

const cssSize = coerceCssPixelValue(sizeInPx);

this._applyProperty(cssFriendlyColumnName, 'flex', `0 0.01 ${cssSize}`);
this.updateTableWidth(delta);
}

applyMinColumnSize(cssFriendlyColumnName: string, _: HTMLElement, sizeInPx: number): void {
Expand All @@ -106,14 +141,23 @@ export class CdkFlexTableResizeStrategy extends ResizeStrategy implements OnDest
return `cdk-column-${cssFriendlyColumnName}`;
}

ngOnDestroy() {
ngOnDestroy(): void {
// TODO: Use remove() once we're off IE11.
if (this._styleElement && this._styleElement.parentNode) {
this._styleElement.parentNode.removeChild(this._styleElement);
this._styleElement = undefined;
}
}

private _getPropertyValue(cssFriendlyColumnName: string, key: string): string|undefined {
const properties = this._getColumnPropertiesMap(cssFriendlyColumnName);
return properties.get(key);
}

private _getAppliedWidth(cssFriendslyColumnName: string): number {
return coercePixelsFromFlexValue(this._getPropertyValue(cssFriendslyColumnName, 'flex'));
}

private _applyProperty(
cssFriendlyColumnName: string,
key: string,
Expand Down Expand Up @@ -166,7 +210,7 @@ export class CdkFlexTableResizeStrategy extends ResizeStrategy implements OnDest
}

const columnClassName = this.getColumnCssClass(cssFriendlyColumnName);
const tableClassName = this._columnResize.getUniqueCssClass();
const tableClassName = this.columnResize.getUniqueCssClass();

const selector = `.${tableClassName} .${columnClassName}`;
const body = propertyKeys.map(key => `${key}:${properties.get(key)}`).join(';');
Expand All @@ -175,6 +219,26 @@ export class CdkFlexTableResizeStrategy extends ResizeStrategy implements OnDest
}
}

/** Converts CSS pixel values to numbers, eg "123px" to 123. Returns NaN for non pixel values. */
function coercePixelsFromCssValue(cssValue: string): number {
return Number(cssValue.match(/(\d+)px/)?.[1]);
}

/** Gets the style.width pixels on the specified element if present, otherwise its offsetWidth. */
function getElementWidth(element: HTMLElement) {
// Optimization: Check style.width first as we probably set it already before reading
// offsetWidth which triggers layout.
return coercePixelsFromCssValue(element.style.width) || element.offsetWidth;
}

/**
* Converts CSS flex values as set in CdkFlexTableResizeStrategy to numbers,
* eg "0 0.01 123px" to 123.
*/
function coercePixelsFromFlexValue(flexValue: string|undefined): number {
return Number(flexValue?.match(/0 0\.01 (\d+)px/)?.[1]);
}

export const TABLE_LAYOUT_FIXED_RESIZE_STRATEGY_PROVIDER: Provider = {
provide: ResizeStrategy,
useClass: TableLayoutFixedResizeStrategy,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
:host {
display: block;
overflow: auto;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<!-- Name Column -->
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef [matResizableMinWidthPx]="100"> Name </mat-header-cell>
<mat-header-cell *matHeaderCellDef [matResizableMinWidthPx]="150"> Name </mat-header-cell>
<mat-cell *matCellDef="let element"> {{element.name}} </mat-cell>
</ng-container>

Expand Down
Loading

0 comments on commit 87b8edb

Please sign in to comment.