diff --git a/src/cdk/drag-drop/directives/drag.ts b/src/cdk/drag-drop/directives/drag.ts index 8ec7fab27f5e..5e1f65bf295b 100644 --- a/src/cdk/drag-drop/directives/drag.ts +++ b/src/cdk/drag-drop/directives/drag.ts @@ -284,6 +284,11 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { this._dragRef.reset(); } + /** Resets drag item to end of boundary element. */ + resetToBoundary() { + this._dragRef.resetToBoundary(); + } + /** * Gets the pixel coordinates of the draggable outside of a drop container. */ diff --git a/src/cdk/drag-drop/directives/standalone-drag.spec.ts b/src/cdk/drag-drop/directives/standalone-drag.spec.ts index 049637833356..71ebf5089ef1 100644 --- a/src/cdk/drag-drop/directives/standalone-drag.spec.ts +++ b/src/cdk/drag-drop/directives/standalone-drag.spec.ts @@ -46,6 +46,26 @@ describe('Standalone CdkDrag', () => { expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); })); + it('should reset drag item to boundary', fakeAsync(() => { + const fixture = createComponent(DragWithResizeableBoundary); + fixture.detectChanges(); + let dragElement = fixture.componentInstance.dragElement.nativeElement; + + expect(dragElement.style.transform).toBeFalsy(); + dragElementViaMouse(fixture, dragElement, 50, 100); + expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)'); + + dragElementViaMouse(fixture, dragElement, 300, 300); + expect(dragElement.style.transform).toBe('translate3d(300px, 300px, 0px)'); + + fixture.componentInstance.resizeBoundary(); + fixture.detectChanges(); + + const position = fixture.componentInstance.dragInstance.getFreeDragPosition(); + expect(position).toEqual({x: 100, y: 0}); + expect(dragElement.style.transform).toBe('translate3d(100px, 0px, 0px)'); + })); + it('should drag an element freely to a particular position when the page is scrolled', fakeAsync(() => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); @@ -2047,3 +2067,26 @@ class PlainStandaloneDraggable { class StandaloneDraggableWithExternalTemplateHandle { @ViewChild('dragElement') dragElement: ElementRef; } + +@Component({ + template: ` +
+
+ I can only be dragged within the dotted container +
+
+ `, + imports: [CdkDrag], +}) +class DragWithResizeableBoundary { + @ViewChild('boundaryElement') boundaryElement: ElementRef; + + @ViewChild('dragElement') dragElement: ElementRef; + @ViewChild(CdkDrag) dragInstance: CdkDrag; + + resizeBoundary() { + this.boundaryElement.nativeElement.style.height = '200px'; + this.boundaryElement.nativeElement.style.width = '200px'; + this.dragInstance.resetToBoundary(); + } +} diff --git a/src/cdk/drag-drop/drag-ref.ts b/src/cdk/drag-drop/drag-ref.ts index 9bcb2e9d5133..c0a1c6b7b23e 100644 --- a/src/cdk/drag-drop/drag-ref.ts +++ b/src/cdk/drag-drop/drag-ref.ts @@ -560,6 +560,19 @@ export class DragRef { this._passiveTransform = {x: 0, y: 0}; } + /** Resets drag item to end of boundary element. */ + resetToBoundary(): void { + // can be null if the drag item was never dragged. + if (this._boundaryElement) { + let x = this._boundaryElement.offsetWidth - this._rootElement.offsetWidth, + y = 0; // align it to the top of its boundary + + this._rootElement.style.transform = getTransform(x, y); + this._activeTransform = {x, y}; + this._passiveTransform = {x, y}; + } + } + /** * Sets a handle as disabled. While a handle is disabled, it'll capture and interrupt dragging. * @param handle Handle element that should be disabled. diff --git a/tools/public_api_guard/cdk/drag-drop.md b/tools/public_api_guard/cdk/drag-drop.md index 28e2c19ceba0..ce05bade8a72 100644 --- a/tools/public_api_guard/cdk/drag-drop.md +++ b/tools/public_api_guard/cdk/drag-drop.md @@ -90,6 +90,7 @@ export class CdkDrag implements AfterViewInit, OnChanges, OnDestroy { _resetPlaceholderTemplate(placeholder: CdkDragPlaceholder): void; // (undocumented) _resetPreviewTemplate(preview: CdkDragPreview): void; + resetToBoundary(): void; rootElementSelector: string; scale: number; setFreeDragPosition(value: Point): void; @@ -443,6 +444,7 @@ export class DragRef { event: MouseEvent | TouchEvent; }>; reset(): void; + resetToBoundary(): void; scale: number; setFreeDragPosition(value: Point): this; _sortFromLastPointerPosition(): void;