Skip to content

Commit 6e79fc0

Browse files
committed
fix(drag-drop): handle the element going out of the boundary after a viewport resize
Adds some logic to ensure that an element inside a boundary is contained within it after the viewport is resized and the boundary may have changed size. Fixes #16536.
1 parent 809d991 commit 6e79fc0

File tree

2 files changed

+132
-0
lines changed

2 files changed

+132
-0
lines changed

src/cdk/drag-drop/directives/drag.spec.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,78 @@ describe('CdkDrag', () => {
763763
expect(dragElement.style.transform).toBe('translate3d(100px, 100px, 0px)');
764764
}));
765765

766+
it('should adjust the x offset if the boundary becomes narrower after a viewport resize',
767+
fakeAsync(() => {
768+
const fixture = createComponent(StandaloneDraggable);
769+
const boundary: HTMLElement = fixture.nativeElement.querySelector('.wrapper');
770+
fixture.componentInstance.boundary = boundary;
771+
fixture.detectChanges();
772+
const dragElement = fixture.componentInstance.dragElement.nativeElement;
773+
774+
dragElementViaMouse(fixture, dragElement, 300, 300);
775+
expect(dragElement.style.transform).toBe('translate3d(100px, 100px, 0px)');
776+
777+
boundary.style.width = '150px';
778+
dispatchFakeEvent(window, 'resize');
779+
tick(20);
780+
781+
expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)');
782+
}));
783+
784+
it('should adjust the y offset if the boundary becomes shorter after a viewport resize',
785+
fakeAsync(() => {
786+
const fixture = createComponent(StandaloneDraggable);
787+
const boundary: HTMLElement = fixture.nativeElement.querySelector('.wrapper');
788+
fixture.componentInstance.boundary = boundary;
789+
fixture.detectChanges();
790+
const dragElement = fixture.componentInstance.dragElement.nativeElement;
791+
792+
dragElementViaMouse(fixture, dragElement, 300, 300);
793+
expect(dragElement.style.transform).toBe('translate3d(100px, 100px, 0px)');
794+
795+
boundary.style.height = '150px';
796+
dispatchFakeEvent(window, 'resize');
797+
tick(20);
798+
799+
expect(dragElement.style.transform).toBe('translate3d(100px, 50px, 0px)');
800+
}));
801+
802+
it('should reset the x offset if the boundary becomes narrower than the element ' +
803+
'after a resize', fakeAsync(() => {
804+
const fixture = createComponent(StandaloneDraggable);
805+
const boundary: HTMLElement = fixture.nativeElement.querySelector('.wrapper');
806+
fixture.componentInstance.boundary = boundary;
807+
fixture.detectChanges();
808+
const dragElement = fixture.componentInstance.dragElement.nativeElement;
809+
810+
dragElementViaMouse(fixture, dragElement, 300, 300);
811+
expect(dragElement.style.transform).toBe('translate3d(100px, 100px, 0px)');
812+
813+
boundary.style.width = '50px';
814+
dispatchFakeEvent(window, 'resize');
815+
tick(20);
816+
817+
expect(dragElement.style.transform).toBe('translate3d(0px, 100px, 0px)');
818+
}));
819+
820+
it('should reset the y offset if the boundary becomes shorter than the element after a resize',
821+
fakeAsync(() => {
822+
const fixture = createComponent(StandaloneDraggable);
823+
const boundary: HTMLElement = fixture.nativeElement.querySelector('.wrapper');
824+
fixture.componentInstance.boundary = boundary;
825+
fixture.detectChanges();
826+
const dragElement = fixture.componentInstance.dragElement.nativeElement;
827+
828+
dragElementViaMouse(fixture, dragElement, 300, 300);
829+
expect(dragElement.style.transform).toBe('translate3d(100px, 100px, 0px)');
830+
831+
boundary.style.height = '50px';
832+
dispatchFakeEvent(window, 'resize');
833+
tick(20);
834+
835+
expect(dragElement.style.transform).toBe('translate3d(100px, 0px, 0px)');
836+
}));
837+
766838
it('should allow for the position constrain logic to be customized', fakeAsync(() => {
767839
const fixture = createComponent(StandaloneDraggable);
768840
const spy = jasmine.createSpy('constrain position spy').and.returnValue({

src/cdk/drag-drop/drag-ref.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,9 @@ export class DragRef<T = any> {
158158
/** Subscription to the viewport being scrolled. */
159159
private _scrollSubscription = Subscription.EMPTY;
160160

161+
/** Subscription to the viewport being resized. */
162+
private _resizeSubscription = Subscription.EMPTY;
163+
161164
/**
162165
* Time at which the last touch event occurred. Used to avoid firing the same
163166
* events multiple times on touch devices where the browser will fire a fake
@@ -351,6 +354,12 @@ export class DragRef<T = any> {
351354
*/
352355
withBoundaryElement(boundaryElement: ElementRef<HTMLElement> | HTMLElement | null): this {
353356
this._boundaryElement = boundaryElement ? coerceElement(boundaryElement) : null;
357+
this._resizeSubscription.unsubscribe();
358+
if (boundaryElement) {
359+
this._resizeSubscription = this._viewportRuler
360+
.change(10)
361+
.subscribe(() => this._containInsideBoundaryOnResize());
362+
}
354363
return this;
355364
}
356365

@@ -1079,6 +1088,57 @@ export class DragRef<T = any> {
10791088

10801089
return {x: 0, y: 0};
10811090
}
1091+
1092+
/**
1093+
* Checks whether the element is still inside its boundary after the viewport has been resized.
1094+
* If not, the position is adjusted so that the element fits again.
1095+
*/
1096+
private _containInsideBoundaryOnResize() {
1097+
let {x, y} = this._passiveTransform;
1098+
1099+
if ((x === 0 && y === 0) || this.isDragging() || !this._boundaryElement) {
1100+
return;
1101+
}
1102+
1103+
const boundaryRect = this._boundaryElement.getBoundingClientRect();
1104+
const elementRect = this._rootElement.getBoundingClientRect();
1105+
const leftOverflow = boundaryRect.left - elementRect.left;
1106+
const rightOverflow = elementRect.right - boundaryRect.right;
1107+
const topOverflow = boundaryRect.top - elementRect.top;
1108+
const bottomOverflow = elementRect.bottom - boundaryRect.bottom;
1109+
1110+
// If the element has become wider than the boundary, we can't
1111+
// do much to make it fit so we just anchor it to the left.
1112+
if (boundaryRect.width > elementRect.width) {
1113+
if (leftOverflow > 0) {
1114+
x += leftOverflow;
1115+
}
1116+
1117+
if (rightOverflow > 0) {
1118+
x -= rightOverflow;
1119+
}
1120+
} else {
1121+
x = 0;
1122+
}
1123+
1124+
// If the element has become taller than the boundary, we can't
1125+
// do much to make it fit so we just anchor it to the left.
1126+
if (boundaryRect.height > elementRect.height) {
1127+
if (topOverflow > 0) {
1128+
y += topOverflow;
1129+
}
1130+
1131+
if (bottomOverflow > 0) {
1132+
y -= bottomOverflow;
1133+
}
1134+
} else {
1135+
y = 0;
1136+
}
1137+
1138+
if (x !== this._passiveTransform.x || y !== this._passiveTransform.y) {
1139+
this.setFreeDragPosition({y, x});
1140+
}
1141+
}
10821142
}
10831143

10841144
/** Point on the page or within an element. */

0 commit comments

Comments
 (0)