From fb7bf900c059214833861d49199ed73d38962f4b Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 11 Sep 2018 19:39:27 +0200 Subject: [PATCH] fix(drag-drop): DOM element not being returned to initial container on drop (#12948) Currently when an item is dropped, we return it to its initial position in the DOM so Angular can take over and re-render it if necessary, however this breaks down if the item is the only child of the drop container and we weren't able to grab a reference element that we can use to restore its position. These changes address the issue by using the initial drop container as a reference. Fixes #12944. --- src/cdk/drag-drop/drag.spec.ts | 72 +++++++++++++++++++++++++++++ src/cdk/drag-drop/drag.ts | 3 +- src/cdk/drag-drop/drop-container.ts | 5 +- 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/src/cdk/drag-drop/drag.spec.ts b/src/cdk/drag-drop/drag.spec.ts index c2949ddc0dd..4e991b0541d 100644 --- a/src/cdk/drag-drop/drag.spec.ts +++ b/src/cdk/drag-drop/drag.spec.ts @@ -1435,6 +1435,45 @@ describe('CdkDrag', () => { }); })); + it('should return DOM element to its initial container after it is dropped, in a container ' + + 'with one draggable item', fakeAsync(() => { + const fixture = createComponent(ConnectedDropZonesWithSingleItems); + fixture.detectChanges(); + + const items = fixture.componentInstance.dragItems.toArray(); + const item = items[0]; + const targetRect = items[1].element.nativeElement.getBoundingClientRect(); + const dropContainers = fixture.componentInstance.dropInstances + .map(drop => drop.element.nativeElement); + + expect(dropContainers[0].contains(item.element.nativeElement)).toBe(true, + 'Expected DOM element to be in first container'); + expect(item.dropContainer).toBe(fixture.componentInstance.dropInstances.first, + 'Expected CdkDrag to be in first container in memory'); + + dragElementViaMouse(fixture, item.element.nativeElement, + targetRect.left + 1, targetRect.top + 1); + flush(); + fixture.detectChanges(); + + expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1); + + const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0]; + + expect(event).toEqual({ + previousIndex: 0, + currentIndex: 0, + item, + container: fixture.componentInstance.dropInstances.toArray()[1], + previousContainer: fixture.componentInstance.dropInstances.first + }); + + expect(dropContainers[0].contains(item.element.nativeElement)).toBe(true, + 'Expected DOM element to be returned to first container'); + expect(item.dropContainer).toBe(fixture.componentInstance.dropInstances.first, + 'Expected CdkDrag to be returned to first container in memory'); + })); + }); }); @@ -1681,6 +1720,39 @@ class DraggableWithAlternateRoot { } +@Component({ + encapsulation: ViewEncapsulation.None, + styles: [` + .cdk-drop { + display: block; + width: 100px; + min-height: ${ITEM_HEIGHT}px; + background: hotpink; + } + + .cdk-drag { + display: block; + height: ${ITEM_HEIGHT}px; + background: red; + } + `], + template: ` + +
One
+
+ + +
Two
+
+ ` +}) +class ConnectedDropZonesWithSingleItems { + @ViewChildren(CdkDrag) dragItems: QueryList; + @ViewChildren(CdkDrop) dropInstances: QueryList; + + droppedSpy = jasmine.createSpy('dropped spy'); +} + /** * Drags an element to a position on the page using the mouse. * @param fixture Fixture on which to run change detection. diff --git a/src/cdk/drag-drop/drag.ts b/src/cdk/drag-drop/drag.ts index 1bfb3d29444..6342a28aa66 100644 --- a/src/cdk/drag-drop/drag.ts +++ b/src/cdk/drag-drop/drag.ts @@ -396,7 +396,7 @@ export class CdkDrag implements AfterViewInit, OnDestroy { if (this._nextSibling) { this._nextSibling.parentNode!.insertBefore(this._rootElement, this._nextSibling); } else { - this._placeholder.parentNode!.appendChild(this._rootElement); + this._initialContainer.element.nativeElement.appendChild(this._rootElement); } this._destroyPreview(); @@ -415,6 +415,7 @@ export class CdkDrag implements AfterViewInit, OnDestroy { previousContainer: this._initialContainer }); this.dropContainer.drop(this, currentIndex, this._initialContainer); + this.dropContainer = this._initialContainer; }); } diff --git a/src/cdk/drag-drop/drop-container.ts b/src/cdk/drag-drop/drop-container.ts index b0881819432..7cf9823e058 100644 --- a/src/cdk/drag-drop/drop-container.ts +++ b/src/cdk/drag-drop/drop-container.ts @@ -6,11 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {InjectionToken, QueryList} from '@angular/core'; +import {InjectionToken, QueryList, ElementRef} from '@angular/core'; import {CdkDrag} from './drag'; export interface CdkDropContainer { + /** DOM node that corresponds to the drop container. */ + element: ElementRef; + /** Arbitrary data to attach to all events emitted by this container. */ data: T;