Skip to content

Commit 881d10b

Browse files
committed
feat(material-experimental/column-resize): Add support for "lazy" rather 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.
1 parent 4ef3baa commit 881d10b

File tree

7 files changed

+103
-19
lines changed

7 files changed

+103
-19
lines changed

renovate.json

Lines changed: 1 addition & 5 deletions
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

Lines changed: 26 additions & 1 deletion
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

Lines changed: 34 additions & 12 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 39 additions & 1 deletion
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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ 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 {ColumnResizeOptions, COLUMN_RESIZE_OPTIONS} from '@angular/cdk-experimental/column-resize';

0 commit comments

Comments
 (0)