Skip to content

Commit

Permalink
feat(material-experimental/column-resize): Add support for "lazy" rat…
Browse files Browse the repository at this point in the history
…her than live updating during resizing.

For complex tables, live resizing is laggy and difficult to use. Keeping the current behavior as default, but we may want to revisit that going forward.
  • Loading branch information
kseamon committed Dec 2, 2024
1 parent 4ef3baa commit 881d10b
Show file tree
Hide file tree
Showing 7 changed files with 103 additions and 19 deletions.
6 changes: 1 addition & 5 deletions renovate.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,7 @@
"matchPackageNames": ["*"]
},
{
"matchPackageNames": [
"@angular/ng-dev",
"@angular/build-tooling",
"angular/dev-infra"
],
"matchPackageNames": ["@angular/ng-dev", "@angular/build-tooling", "angular/dev-infra"],
"groupName": "angular shared dev-infra code",
"enabled": true
},
Expand Down
27 changes: 26 additions & 1 deletion src/cdk-experimental/column-resize/column-resize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@
* found in the LICENSE file at https://angular.dev/license
*/

import {AfterViewInit, Directive, ElementRef, inject, NgZone, OnDestroy} from '@angular/core';
import {
AfterViewInit,
Directive,
ElementRef,
inject,
InjectionToken,
Input,
NgZone,
OnDestroy,
} from '@angular/core';
import {_IdGenerator} from '@angular/cdk/a11y';
import {fromEvent, merge, Subject} from 'rxjs';
import {filter, map, mapTo, pairwise, startWith, take, takeUntil} from 'rxjs/operators';
Expand All @@ -20,6 +29,15 @@ import {HeaderRowEventDispatcher} from './event-dispatcher';
const HOVER_OR_ACTIVE_CLASS = 'cdk-column-resize-hover-or-active';
const WITH_RESIZED_COLUMN_CLASS = 'cdk-column-resize-with-resized-column';

/** Configurable options for column resize. */
export interface ColumnResizeOptions {
liveResizeUpdates?: boolean; // Defaults to true.
}

export const COLUMN_RESIZE_OPTIONS = new InjectionToken<ColumnResizeOptions>(
'CdkColumnResizeOptions',
);

/**
* Base class for ColumnResize directives which attach to mat-table elements to
* provide common events and services for column resizing.
Expand All @@ -45,6 +63,13 @@ export abstract class ColumnResize implements AfterViewInit, OnDestroy {
/** The id attribute of the table, if specified. */
id?: string;

/**
* Whether to update the column's width continuously as the mouse position
* changes, or to wait until mouseup to apply the new size.
*/
@Input() liveResizeUpdates =
inject(COLUMN_RESIZE_OPTIONS, {optional: true})?.liveResizeUpdates ?? true;

ngAfterViewInit() {
this.elementRef.nativeElement!.classList.add(this.getUniqueCssClass());

Expand Down
46 changes: 34 additions & 12 deletions src/cdk-experimental/column-resize/overlay-handle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export abstract class ResizeOverlayHandle implements AfterViewInit, OnDestroy {
protected abstract readonly resizeRef: ResizeRef;
protected abstract readonly styleScheduler: _CoalescedStyleScheduler;

private _cumulativeDeltaX = 0;

ngAfterViewInit() {
this._listenForMouseEvents();
}
Expand Down Expand Up @@ -101,6 +103,7 @@ export abstract class ResizeOverlayHandle implements AfterViewInit, OnDestroy {
let originOffset = this._getOriginOffset();
let size = initialSize;
let overshot = 0;
this._cumulativeDeltaX = 0;

this.updateResizeActive(true);

Expand All @@ -125,6 +128,14 @@ export abstract class ResizeOverlayHandle implements AfterViewInit, OnDestroy {
.subscribe(([prevX, currX]) => {
let deltaX = currX - prevX;

if (!this.resizeRef.liveUpdates) {
this._cumulativeDeltaX += deltaX;
const sizeDelta = this._computeNewSize(size, this._cumulativeDeltaX) - size;
this._updateOverlayOffset(sizeDelta);

return;
}

// If the mouse moved further than the resize was able to match, limit the
// movement of the overlay to match the actual size and position of the origin.
if (overshot !== 0) {
Expand All @@ -143,18 +154,7 @@ export abstract class ResizeOverlayHandle implements AfterViewInit, OnDestroy {
}
}

let computedNewSize: number = size + (this._isLtr() ? deltaX : -deltaX);
computedNewSize = Math.min(
Math.max(computedNewSize, this.resizeRef.minWidthPx, 0),
this.resizeRef.maxWidthPx,
);

this.resizeNotifier.triggerResize.next({
columnId: this.columnDef.name,
size: computedNewSize,
previousSize: size,
isStickyColumn: this.columnDef.sticky || this.columnDef.stickyEnd,
});
this._triggerResize(size, deltaX);

this.styleScheduler.scheduleEnd(() => {
const originNewSize = this._getOriginWidth();
Expand All @@ -178,6 +178,24 @@ export abstract class ResizeOverlayHandle implements AfterViewInit, OnDestroy {
);
}

private _triggerResize(startSize: number, deltaX: number): void {
this.resizeNotifier.triggerResize.next({
columnId: this.columnDef.name,
size: this._computeNewSize(startSize, deltaX),
previousSize: startSize,
isStickyColumn: this.columnDef.sticky || this.columnDef.stickyEnd,
});
}

private _computeNewSize(startSize: number, deltaX: number): number {
let computedNewSize: number = startSize + (this._isLtr() ? deltaX : -deltaX);
computedNewSize = Math.min(
Math.max(computedNewSize, this.resizeRef.minWidthPx, 0),
this.resizeRef.maxWidthPx,
);
return computedNewSize;
}

private _getOriginWidth(): number {
return this.resizeRef.origin.nativeElement!.offsetWidth;
}
Expand All @@ -202,6 +220,10 @@ export abstract class ResizeOverlayHandle implements AfterViewInit, OnDestroy {
this.ngZone.run(() => {
const sizeMessage = {columnId: this.columnDef.name, size};
if (completedSuccessfully) {
if (!this.resizeRef.liveUpdates) {
this._triggerResize(size, this._cumulativeDeltaX);
}

this.resizeNotifier.resizeCompleted.next(sizeMessage);
} else {
this.resizeNotifier.resizeCanceled.next(sizeMessage);
Expand Down
1 change: 1 addition & 0 deletions src/cdk-experimental/column-resize/resizable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ export abstract class Resizable<HandleComponent extends ResizeOverlayHandle>
this.overlayRef!,
this.minWidthPx,
this.maxWidthPx,
this.columnResize.liveResizeUpdates,
),
},
],
Expand Down
1 change: 1 addition & 0 deletions src/cdk-experimental/column-resize/resize-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ export class ResizeRef {
readonly overlayRef: OverlayRef,
readonly minWidthPx: number,
readonly maxWidthPx: number,
readonly liveUpdates = true,
) {}
}
40 changes: 39 additions & 1 deletion src/material-experimental/column-resize/column-resize.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ describe('Material Popover Edit', () => {
expect(component.getOverlayThumbElement(0)).toBeUndefined();
}));

it('resizes the target column via mouse input', fakeAsync(() => {
it('resizes the target column via mouse input (live updates)', fakeAsync(() => {
const initialTableWidth = component.getTableWidth();
const initialColumnWidth = component.getColumnWidth(1);
const initialColumnPosition = component.getColumnOriginPosition(1);
Expand Down Expand Up @@ -485,6 +485,44 @@ describe('Material Popover Edit', () => {
fixture.detectChanges();
}));

it('resizes the target column via mouse input (no live update)', fakeAsync(() => {
const initialTableWidth = component.getTableWidth();
const initialColumnWidth = component.getColumnWidth(1);

component.columnResize.liveResizeUpdates = false;

component.triggerHoverState();
fixture.detectChanges();
component.beginColumnResizeWithMouse(1);

const initialThumbPosition = component.getOverlayThumbPosition(1);
component.updateResizeWithMouseInProgress(5);
fixture.detectChanges();
flush();

let thumbPositionDelta = component.getOverlayThumbPosition(1) - initialThumbPosition;
(expect(thumbPositionDelta) as any).isApproximately(5);
(expect(component.getColumnWidth(1)) as any).toBe(initialColumnWidth);

component.updateResizeWithMouseInProgress(1);
fixture.detectChanges();
flush();

thumbPositionDelta = component.getOverlayThumbPosition(1) - initialThumbPosition;

(expect(component.getTableWidth()) as any).toBe(initialTableWidth);
(expect(component.getColumnWidth(1)) as any).toBe(initialColumnWidth);

component.completeResizeWithMouseInProgress(1);
flush();

(expect(component.getTableWidth()) as any).isApproximately(initialTableWidth + 1);
(expect(component.getColumnWidth(1)) as any).isApproximately(initialColumnWidth + 1);

component.endHoverState();
fixture.detectChanges();
}));

it('should not start dragging using the right mouse button', fakeAsync(() => {
const initialColumnWidth = component.getColumnWidth(1);

Expand Down
1 change: 1 addition & 0 deletions src/material-experimental/column-resize/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ export * from './resizable-directives/default-enabled-resizable';
export * from './resizable-directives/resizable';
export * from './resize-strategy';
export * from './overlay-handle';
export {ColumnResizeOptions, COLUMN_RESIZE_OPTIONS} from '@angular/cdk-experimental/column-resize';

0 comments on commit 881d10b

Please sign in to comment.