Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(drag-drop): handle the element going out of the boundary after a viewport resize #16874

Merged
merged 1 commit into from
Sep 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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