Skip to content

Commit

Permalink
fix(drag-drop): handle the element going out of the boundary after a …
Browse files Browse the repository at this point in the history
…viewport resize (angular#16874)

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 angular#16536.
  • Loading branch information
crisbeto authored and jelbourn committed Sep 6, 2019
1 parent 83545cf commit 44b8a47
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 0 deletions.
72 changes: 72 additions & 0 deletions src/cdk/drag-drop/directives/drag.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,24 @@ describe('CdkDrag', () => {
expect(dragElement.style.transform).toBe('translate3d(100px, 100px, 0px)');
}));

it('should adjust the x offset if the boundary becomes narrower after a viewport resize',
fakeAsync(() => {
const fixture = createComponent(StandaloneDraggable);
const boundary: HTMLElement = fixture.nativeElement.querySelector('.wrapper');
fixture.componentInstance.boundary = boundary;
fixture.detectChanges();
const dragElement = fixture.componentInstance.dragElement.nativeElement;

dragElementViaMouse(fixture, dragElement, 300, 300);
expect(dragElement.style.transform).toBe('translate3d(100px, 100px, 0px)');

boundary.style.width = '150px';
dispatchFakeEvent(window, 'resize');
tick(20);

expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)');
}));

it('should handle the element and boundary dimensions changing between drag sequences',
fakeAsync(() => {
const fixture = createComponent(StandaloneDraggable);
Expand All @@ -782,6 +800,60 @@ describe('CdkDrag', () => {
expect(dragElement.style.transform).toBe('translate3d(150px, 150px, 0px)');
}));

it('should adjust the y offset if the boundary becomes shorter after a viewport resize',
fakeAsync(() => {
const fixture = createComponent(StandaloneDraggable);
const boundary: HTMLElement = fixture.nativeElement.querySelector('.wrapper');
fixture.componentInstance.boundary = boundary;
fixture.detectChanges();
const dragElement = fixture.componentInstance.dragElement.nativeElement;

dragElementViaMouse(fixture, dragElement, 300, 300);
expect(dragElement.style.transform).toBe('translate3d(100px, 100px, 0px)');

boundary.style.height = '150px';
dispatchFakeEvent(window, 'resize');
tick(20);

expect(dragElement.style.transform).toBe('translate3d(100px, 50px, 0px)');
}));

it('should reset the x offset if the boundary becomes narrower than the element ' +
'after a resize', fakeAsync(() => {
const fixture = createComponent(StandaloneDraggable);
const boundary: HTMLElement = fixture.nativeElement.querySelector('.wrapper');
fixture.componentInstance.boundary = boundary;
fixture.detectChanges();
const dragElement = fixture.componentInstance.dragElement.nativeElement;

dragElementViaMouse(fixture, dragElement, 300, 300);
expect(dragElement.style.transform).toBe('translate3d(100px, 100px, 0px)');

boundary.style.width = '50px';
dispatchFakeEvent(window, 'resize');
tick(20);

expect(dragElement.style.transform).toBe('translate3d(0px, 100px, 0px)');
}));

it('should reset the y offset if the boundary becomes shorter than the element after a resize',
fakeAsync(() => {
const fixture = createComponent(StandaloneDraggable);
const boundary: HTMLElement = fixture.nativeElement.querySelector('.wrapper');
fixture.componentInstance.boundary = boundary;
fixture.detectChanges();
const dragElement = fixture.componentInstance.dragElement.nativeElement;

dragElementViaMouse(fixture, dragElement, 300, 300);
expect(dragElement.style.transform).toBe('translate3d(100px, 100px, 0px)');

boundary.style.height = '50px';
dispatchFakeEvent(window, 'resize');
tick(20);

expect(dragElement.style.transform).toBe('translate3d(100px, 0px, 0px)');
}));

it('should allow for the position constrain logic to be customized', fakeAsync(() => {
const fixture = createComponent(StandaloneDraggable);
const spy = jasmine.createSpy('constrain position spy').and.returnValue({
Expand Down
60 changes: 60 additions & 0 deletions src/cdk/drag-drop/drag-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,9 @@ export class DragRef<T = any> {
/** Subscription to the viewport being scrolled. */
private _scrollSubscription = Subscription.EMPTY;

/** Subscription to the viewport being resized. */
private _resizeSubscription = Subscription.EMPTY;

/**
* Time at which the last touch event occurred. Used to avoid firing the same
* events multiple times on touch devices where the browser will fire a fake
Expand Down Expand Up @@ -351,6 +354,12 @@ export class DragRef<T = any> {
*/
withBoundaryElement(boundaryElement: ElementRef<HTMLElement> | HTMLElement | null): this {
this._boundaryElement = boundaryElement ? coerceElement(boundaryElement) : null;
this._resizeSubscription.unsubscribe();
if (boundaryElement) {
this._resizeSubscription = this._viewportRuler
.change(10)
.subscribe(() => this._containInsideBoundaryOnResize());
}
return this;
}

Expand Down Expand Up @@ -1086,6 +1095,57 @@ export class DragRef<T = any> {
private _cleanupCachedDimensions() {
this._boundaryRect = this._previewRect = undefined;
}

/**
* Checks whether the element is still inside its boundary after the viewport has been resized.
* If not, the position is adjusted so that the element fits again.
*/
private _containInsideBoundaryOnResize() {
let {x, y} = this._passiveTransform;

if ((x === 0 && y === 0) || this.isDragging() || !this._boundaryElement) {
return;
}

const boundaryRect = this._boundaryElement.getBoundingClientRect();
const elementRect = this._rootElement.getBoundingClientRect();
const leftOverflow = boundaryRect.left - elementRect.left;
const rightOverflow = elementRect.right - boundaryRect.right;
const topOverflow = boundaryRect.top - elementRect.top;
const bottomOverflow = elementRect.bottom - boundaryRect.bottom;

// If the element has become wider than the boundary, we can't
// do much to make it fit so we just anchor it to the left.
if (boundaryRect.width > elementRect.width) {
if (leftOverflow > 0) {
x += leftOverflow;
}

if (rightOverflow > 0) {
x -= rightOverflow;
}
} else {
x = 0;
}

// If the element has become taller than the boundary, we can't
// do much to make it fit so we just anchor it to the top.
if (boundaryRect.height > elementRect.height) {
if (topOverflow > 0) {
y += topOverflow;
}

if (bottomOverflow > 0) {
y -= bottomOverflow;
}
} else {
y = 0;
}

if (x !== this._passiveTransform.x || y !== this._passiveTransform.y) {
this.setFreeDragPosition({y, x});
}
}
}

/** Point on the page or within an element. */
Expand Down

0 comments on commit 44b8a47

Please sign in to comment.