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. (#30120)

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 authored Dec 4, 2024
1 parent 5a51819 commit 8685c01
Show file tree
Hide file tree
Showing 7 changed files with 104 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
2 changes: 2 additions & 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,5 @@ export * from './resizable-directives/default-enabled-resizable';
export * from './resizable-directives/resizable';
export * from './resize-strategy';
export * from './overlay-handle';
export type {ColumnResizeOptions} from '@angular/cdk-experimental/column-resize';
export {COLUMN_RESIZE_OPTIONS} from '@angular/cdk-experimental/column-resize';

0 comments on commit 8685c01

Please sign in to comment.