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.
This commit is contained in:
Kristiyan Kostadinov
2018-09-11 19:39:27 +02:00
committed by Jeremy Elbourn
parent d1c27ae1c4
commit fb7bf900c0
3 changed files with 78 additions and 2 deletions
+72
View File
@@ -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: `
<cdk-drop #todoZone [connectedTo]="[doneZone]" (dropped)="droppedSpy($event)">
<div cdkDrag>One</div>
</cdk-drop>
<cdk-drop #doneZone [connectedTo]="[todoZone]" (dropped)="droppedSpy($event)">
<div cdkDrag>Two</div>
</cdk-drop>
`
})
class ConnectedDropZonesWithSingleItems {
@ViewChildren(CdkDrag) dragItems: QueryList<CdkDrag>;
@ViewChildren(CdkDrop) dropInstances: QueryList<CdkDrop>;
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.
+2 -1
View File
@@ -396,7 +396,7 @@ export class CdkDrag<T = any> 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<T = any> implements AfterViewInit, OnDestroy {
previousContainer: this._initialContainer
});
this.dropContainer.drop(this, currentIndex, this._initialContainer);
this.dropContainer = this._initialContainer;
});
}
+4 -1
View File
@@ -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<T = any> {
/** DOM node that corresponds to the drop container. */
element: ElementRef<HTMLElement>;
/** Arbitrary data to attach to all events emitted by this container. */
data: T;