Skip to content

Commit 8685c01

Browse files
authored
feat(material-experimental/column-resize): Add support for "lazy" rather than live updating during resizing. (angular#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.
1 parent 5a51819 commit 8685c01

File tree

7 files changed

+104
-19
lines changed

7 files changed

+104
-19
lines changed

renovate.json

+1-5
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,7 @@
2424
"matchPackageNames": ["*"]
2525
},
2626
{
27-
"matchPackageNames": [
28-
"@angular/ng-dev",
29-
"@angular/build-tooling",
30-
"angular/dev-infra"
31-
],
27+
"matchPackageNames": ["@angular/ng-dev", "@angular/build-tooling", "angular/dev-infra"],
3228
"groupName": "angular shared dev-infra code",
3329
"enabled": true
3430
},

src/cdk-experimental/column-resize/column-resize.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,16 @@
66
* found in the LICENSE file at https://angular.dev/license
77
*/
88

9-
import {AfterViewInit, Directive, ElementRef, inject, NgZone, OnDestroy} from '@angular/core';
9+
import {
10+
AfterViewInit,
11+
Directive,
12+
ElementRef,
13+
inject,
14+
InjectionToken,
15+
Input,
16+
NgZone,
17+
OnDestroy,
18+
} from '@angular/core';
1019
import {_IdGenerator} from '@angular/cdk/a11y';
1120
import {fromEvent, merge, Subject} from 'rxjs';
1221
import {filter, map, mapTo, pairwise, startWith, take, takeUntil} from 'rxjs/operators';
@@ -20,6 +29,15 @@ import {HeaderRowEventDispatcher} from './event-dispatcher';
2029
const HOVER_OR_ACTIVE_CLASS = 'cdk-column-resize-hover-or-active';
2130
const WITH_RESIZED_COLUMN_CLASS = 'cdk-column-resize-with-resized-column';
2231

32+
/** Configurable options for column resize. */
33+
export interface ColumnResizeOptions {
34+
liveResizeUpdates?: boolean; // Defaults to true.
35+
}
36+
37+
export const COLUMN_RESIZE_OPTIONS = new InjectionToken<ColumnResizeOptions>(
38+
'CdkColumnResizeOptions',
39+
);
40+
2341
/**
2442
* Base class for ColumnResize directives which attach to mat-table elements to
2543
* provide common events and services for column resizing.
@@ -45,6 +63,13 @@ export abstract class ColumnResize implements AfterViewInit, OnDestroy {
4563
/** The id attribute of the table, if specified. */
4664
id?: string;
4765

66+
/**
67+
* Whether to update the column's width continuously as the mouse position
68+
* changes, or to wait until mouseup to apply the new size.
69+
*/
70+
@Input() liveResizeUpdates =
71+
inject(COLUMN_RESIZE_OPTIONS, {optional: true})?.liveResizeUpdates ?? true;
72+
4873
ngAfterViewInit() {
4974
this.elementRef.nativeElement!.classList.add(this.getUniqueCssClass());
5075

src/cdk-experimental/column-resize/overlay-handle.ts

+34-12
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ export abstract class ResizeOverlayHandle implements AfterViewInit, OnDestroy {
4949
protected abstract readonly resizeRef: ResizeRef;
5050
protected abstract readonly styleScheduler: _CoalescedStyleScheduler;
5151

52+
private _cumulativeDeltaX = 0;
53+
5254
ngAfterViewInit() {
5355
this._listenForMouseEvents();
5456
}
@@ -101,6 +103,7 @@ export abstract class ResizeOverlayHandle implements AfterViewInit, OnDestroy {
101103
let originOffset = this._getOriginOffset();
102104
let size = initialSize;
103105
let overshot = 0;
106+
this._cumulativeDeltaX = 0;
104107

105108
this.updateResizeActive(true);
106109

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

131+
if (!this.resizeRef.liveUpdates) {
132+
this._cumulativeDeltaX += deltaX;
133+
const sizeDelta = this._computeNewSize(size, this._cumulativeDeltaX) - size;
134+
this._updateOverlayOffset(sizeDelta);
135+
136+
return;
137+
}
138+
128139
// If the mouse moved further than the resize was able to match, limit the
129140
// movement of the overlay to match the actual size and position of the origin.
130141
if (overshot !== 0) {
@@ -143,18 +154,7 @@ export abstract class ResizeOverlayHandle implements AfterViewInit, OnDestroy {
143154
}
144155
}
145156

146-
let computedNewSize: number = size + (this._isLtr() ? deltaX : -deltaX);
147-
computedNewSize = Math.min(
148-
Math.max(computedNewSize, this.resizeRef.minWidthPx, 0),
149-
this.resizeRef.maxWidthPx,
150-
);
151-
152-
this.resizeNotifier.triggerResize.next({
153-
columnId: this.columnDef.name,
154-
size: computedNewSize,
155-
previousSize: size,
156-
isStickyColumn: this.columnDef.sticky || this.columnDef.stickyEnd,
157-
});
157+
this._triggerResize(size, deltaX);
158158

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

181+
private _triggerResize(startSize: number, deltaX: number): void {
182+
this.resizeNotifier.triggerResize.next({
183+
columnId: this.columnDef.name,
184+
size: this._computeNewSize(startSize, deltaX),
185+
previousSize: startSize,
186+
isStickyColumn: this.columnDef.sticky || this.columnDef.stickyEnd,
187+
});
188+
}
189+
190+
private _computeNewSize(startSize: number, deltaX: number): number {
191+
let computedNewSize: number = startSize + (this._isLtr() ? deltaX : -deltaX);
192+
computedNewSize = Math.min(
193+
Math.max(computedNewSize, this.resizeRef.minWidthPx, 0),
194+
this.resizeRef.maxWidthPx,
195+
);
196+
return computedNewSize;
197+
}
198+
181199
private _getOriginWidth(): number {
182200
return this.resizeRef.origin.nativeElement!.offsetWidth;
183201
}
@@ -202,6 +220,10 @@ export abstract class ResizeOverlayHandle implements AfterViewInit, OnDestroy {
202220
this.ngZone.run(() => {
203221
const sizeMessage = {columnId: this.columnDef.name, size};
204222
if (completedSuccessfully) {
223+
if (!this.resizeRef.liveUpdates) {
224+
this._triggerResize(size, this._cumulativeDeltaX);
225+
}
226+
205227
this.resizeNotifier.resizeCompleted.next(sizeMessage);
206228
} else {
207229
this.resizeNotifier.resizeCanceled.next(sizeMessage);

src/cdk-experimental/column-resize/resizable.ts

+1
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ export abstract class Resizable<HandleComponent extends ResizeOverlayHandle>
230230
this.overlayRef!,
231231
this.minWidthPx,
232232
this.maxWidthPx,
233+
this.columnResize.liveResizeUpdates,
233234
),
234235
},
235236
],

src/cdk-experimental/column-resize/resize-ref.ts

+1
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,6 @@ export class ResizeRef {
1616
readonly overlayRef: OverlayRef,
1717
readonly minWidthPx: number,
1818
readonly maxWidthPx: number,
19+
readonly liveUpdates = true,
1920
) {}
2021
}

src/material-experimental/column-resize/column-resize.spec.ts

+39-1
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,7 @@ describe('Material Popover Edit', () => {
436436
expect(component.getOverlayThumbElement(0)).toBeUndefined();
437437
}));
438438

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

488+
it('resizes the target column via mouse input (no live update)', fakeAsync(() => {
489+
const initialTableWidth = component.getTableWidth();
490+
const initialColumnWidth = component.getColumnWidth(1);
491+
492+
component.columnResize.liveResizeUpdates = false;
493+
494+
component.triggerHoverState();
495+
fixture.detectChanges();
496+
component.beginColumnResizeWithMouse(1);
497+
498+
const initialThumbPosition = component.getOverlayThumbPosition(1);
499+
component.updateResizeWithMouseInProgress(5);
500+
fixture.detectChanges();
501+
flush();
502+
503+
let thumbPositionDelta = component.getOverlayThumbPosition(1) - initialThumbPosition;
504+
(expect(thumbPositionDelta) as any).isApproximately(5);
505+
(expect(component.getColumnWidth(1)) as any).toBe(initialColumnWidth);
506+
507+
component.updateResizeWithMouseInProgress(1);
508+
fixture.detectChanges();
509+
flush();
510+
511+
thumbPositionDelta = component.getOverlayThumbPosition(1) - initialThumbPosition;
512+
513+
(expect(component.getTableWidth()) as any).toBe(initialTableWidth);
514+
(expect(component.getColumnWidth(1)) as any).toBe(initialColumnWidth);
515+
516+
component.completeResizeWithMouseInProgress(1);
517+
flush();
518+
519+
(expect(component.getTableWidth()) as any).isApproximately(initialTableWidth + 1);
520+
(expect(component.getColumnWidth(1)) as any).isApproximately(initialColumnWidth + 1);
521+
522+
component.endHoverState();
523+
fixture.detectChanges();
524+
}));
525+
488526
it('should not start dragging using the right mouse button', fakeAsync(() => {
489527
const initialColumnWidth = component.getColumnWidth(1);
490528

src/material-experimental/column-resize/public-api.ts

+2
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,5 @@ export * from './resizable-directives/default-enabled-resizable';
1515
export * from './resizable-directives/resizable';
1616
export * from './resize-strategy';
1717
export * from './overlay-handle';
18+
export type {ColumnResizeOptions} from '@angular/cdk-experimental/column-resize';
19+
export {COLUMN_RESIZE_OPTIONS} from '@angular/cdk-experimental/column-resize';

0 commit comments

Comments
 (0)