feat(drag-drop): indicate in dropped event whether item was dropped outside of container (#14140)

Adds an extra flag on the `CdkDragDrop` event that indicates whether the user's pointer was over the container when they dropped an item.

Fixes #14136.
This commit is contained in:
Kristiyan Kostadinov
2018-11-28 16:56:29 +01:00
committed by Jeremy Elbourn
parent acaed95b0d
commit 42de6a2c98
6 changed files with 103 additions and 38 deletions
+2
View File
@@ -53,6 +53,8 @@ export interface CdkDragDrop<T, O = T> {
container: CdkDropListContainer<T>;
/** Container from which the item was picked up. Can be the same as the `container`. */
previousContainer: CdkDropListContainer<O>;
/** Whether the user's pointer was over the container when the item was dropped. */
isPointerOverContainer: boolean;
}
/** Event emitted as the user is dragging a draggable item. */
+69 -15
View File
@@ -776,13 +776,55 @@ describe('CdkDrag', () => {
currentIndex: 2,
item: firstItem,
container: fixture.componentInstance.dropInstance,
previousContainer: fixture.componentInstance.dropInstance
previousContainer: fixture.componentInstance.dropInstance,
isPointerOverContainer: true
});
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
.toEqual(['One', 'Two', 'Zero', 'Three']);
}));
it('should expose whether an item was dropped over a container', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZone);
fixture.detectChanges();
const dragItems = fixture.componentInstance.dragItems;
const firstItem = dragItems.first;
const thirdItemRect = dragItems.toArray()[2].element.nativeElement.getBoundingClientRect();
dragElementViaMouse(fixture, firstItem.element.nativeElement,
thirdItemRect.left + 1, thirdItemRect.top + 1);
flush();
fixture.detectChanges();
expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1);
const event: CdkDragDrop<any> =
fixture.componentInstance.droppedSpy.calls.mostRecent().args[0];
expect(event.isPointerOverContainer).toBe(true);
}));
it('should expose whether an item was dropped outside of a container', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZone);
fixture.detectChanges();
const dragItems = fixture.componentInstance.dragItems;
const firstItem = dragItems.first;
const containerRect = fixture.componentInstance.dropInstance.element
.nativeElement.getBoundingClientRect();
dragElementViaMouse(fixture, firstItem.element.nativeElement,
containerRect.right + 10, containerRect.bottom + 10);
flush();
fixture.detectChanges();
expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1);
const event: CdkDragDrop<any> =
fixture.componentInstance.droppedSpy.calls.mostRecent().args[0];
expect(event.isPointerOverContainer).toBe(false);
}));
it('should dispatch the `sorted` event as an item is being sorted', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZone);
fixture.detectChanges();
@@ -841,7 +883,8 @@ describe('CdkDrag', () => {
currentIndex: 0,
item: firstItem,
container: fixture.componentInstance.dropInstance,
previousContainer: fixture.componentInstance.dropInstance
previousContainer: fixture.componentInstance.dropInstance,
isPointerOverContainer: false
});
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
@@ -898,7 +941,8 @@ describe('CdkDrag', () => {
currentIndex: 2,
item: firstItem,
container: fixture.componentInstance.dropInstance,
previousContainer: fixture.componentInstance.dropInstance
previousContainer: fixture.componentInstance.dropInstance,
isPointerOverContainer: true
});
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
@@ -937,7 +981,8 @@ describe('CdkDrag', () => {
currentIndex: 2,
item: firstItem,
container: fixture.componentInstance.dropInstance,
previousContainer: fixture.componentInstance.dropInstance
previousContainer: fixture.componentInstance.dropInstance,
isPointerOverContainer: true
});
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
@@ -972,7 +1017,8 @@ describe('CdkDrag', () => {
currentIndex: 0,
item: firstItem,
container: fixture.componentInstance.dropInstance,
previousContainer: fixture.componentInstance.dropInstance
previousContainer: fixture.componentInstance.dropInstance,
isPointerOverContainer: false
});
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
@@ -1808,7 +1854,8 @@ describe('CdkDrag', () => {
currentIndex: 3,
item,
container: fixture.componentInstance.dropInstances.toArray()[1],
previousContainer: fixture.componentInstance.dropInstances.first
previousContainer: fixture.componentInstance.dropInstances.first,
isPointerOverContainer: true
});
}));
@@ -1909,7 +1956,8 @@ describe('CdkDrag', () => {
currentIndex: 3,
item: groups[0][1],
container: dropInstances[1],
previousContainer: dropInstances[0]
previousContainer: dropInstances[0],
isPointerOverContainer: true
});
}));
@@ -1938,7 +1986,8 @@ describe('CdkDrag', () => {
currentIndex: 1,
item: groups[0][1],
container: dropInstances[0],
previousContainer: dropInstances[0]
previousContainer: dropInstances[0],
isPointerOverContainer: false
});
}));
@@ -1967,7 +2016,8 @@ describe('CdkDrag', () => {
currentIndex: 1,
item: groups[0][1],
container: dropInstances[0],
previousContainer: dropInstances[0]
previousContainer: dropInstances[0],
isPointerOverContainer: false
});
}));
@@ -2089,7 +2139,8 @@ describe('CdkDrag', () => {
currentIndex: 3,
item: groups[0][1],
container: dropInstances[1],
previousContainer: dropInstances[0]
previousContainer: dropInstances[0],
isPointerOverContainer: true
});
}));
@@ -2114,7 +2165,8 @@ describe('CdkDrag', () => {
currentIndex: 3,
item: groups[0][1],
container: dropInstances[1],
previousContainer: dropInstances[0]
previousContainer: dropInstances[0],
isPointerOverContainer: true
});
}));
@@ -2144,7 +2196,8 @@ describe('CdkDrag', () => {
currentIndex: 3,
item: groups[0][1],
container: dropInstances[1],
previousContainer: dropInstances[0]
previousContainer: dropInstances[0],
isPointerOverContainer: true
});
}));
@@ -2178,7 +2231,8 @@ describe('CdkDrag', () => {
currentIndex: 0,
item,
container: fixture.componentInstance.dropInstances.toArray()[1],
previousContainer: fixture.componentInstance.dropInstances.first
previousContainer: fixture.componentInstance.dropInstances.first,
isPointerOverContainer: true
});
expect(dropContainers[0].contains(item.element.nativeElement)).toBe(true,
@@ -2667,7 +2721,7 @@ function dragElementViaMouse(fixture: ComponentFixture<any>,
dispatchMouseEvent(document, 'mousemove', x, y);
fixture.detectChanges();
dispatchMouseEvent(document, 'mouseup');
dispatchMouseEvent(document, 'mouseup', x, y);
fixture.detectChanges();
}
@@ -2706,7 +2760,7 @@ function dragElementViaTouch(fixture: ComponentFixture<any>,
dispatchTouchEvent(document, 'touchmove', x, y);
fixture.detectChanges();
dispatchTouchEvent(document, 'touchend');
dispatchTouchEvent(document, 'touchend', x, y);
fixture.detectChanges();
}
+12 -9
View File
@@ -515,7 +515,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
}
/** Handler that is invoked when the user lifts their pointer up, after initiating a drag. */
private _pointerUp = () => {
private _pointerUp = (event: MouseEvent | TouchEvent) => {
if (!this._isDragging()) {
return;
}
@@ -539,13 +539,13 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
}
this._animatePreviewToPlaceholder().then(() => {
this._cleanupDragArtifacts();
this._cleanupDragArtifacts(event);
this._dragDropRegistry.stopDragging(this);
});
}
/** Cleans up the DOM artifacts that were added to facilitate the element being dragged. */
private _cleanupDragArtifacts() {
private _cleanupDragArtifacts(event: MouseEvent | TouchEvent) {
// Restore the element's visibility and insert it at its old position in the DOM.
// It's important that we maintain the position, because moving the element around in the DOM
// can throw off `NgFor` which does smart diffing and re-creates elements only when necessary,
@@ -564,6 +564,8 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
// Re-enter the NgZone since we bound `document` events on the outside.
this._ngZone.run(() => {
const currentIndex = this.dropContainer.getItemIndex(this);
const {x, y} = this._getPointerPositionOnPage(event);
const isPointerOverContainer = this.dropContainer._isOverContainer(x, y);
this.ended.emit({source: this});
this.dropped.emit({
@@ -571,9 +573,10 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
currentIndex,
previousIndex: this._initialContainer.getItemIndex(this),
container: this.dropContainer,
previousContainer: this._initialContainer
previousContainer: this._initialContainer,
isPointerOverContainer
});
this.dropContainer.drop(this, currentIndex, this._initialContainer);
this.dropContainer.drop(this, currentIndex, this._initialContainer, isPointerOverContainer);
this.dropContainer = this._initialContainer;
});
}
@@ -587,11 +590,11 @@ export class CdkDrag<T = any> implements AfterViewInit, OnDestroy {
let newContainer = this.dropContainer._getSiblingContainerFromPosition(this, x, y);
// If we couldn't find a new container to move the item into, and the item has left it's
// initial container, check whether the it's allowed to return into its original container.
// This handles the case where two containers are connected one way and the user tries to
// undo dragging an item into a new container.
// initial container, check whether the it's over the initial container. This handles the
// case where two containers are connected one way and the user tries to undo dragging an
// item into a new container.
if (!newContainer && this.dropContainer !== this._initialContainer &&
this._initialContainer._canReturnItem(x, y)) {
this._initialContainer._isOverContainer(x, y)) {
newContainer = this._initialContainer;
}
+5 -2
View File
@@ -37,8 +37,11 @@ export interface CdkDropListContainer<T = any> {
* @param item Item being dropped into the container.
* @param currentIndex Index at which the item should be inserted.
* @param previousContainer Container from which the item got dragged in.
* @param isPointerOverContainer Whether the user's pointer was over the
* container when the item was dropped.
*/
drop(item: CdkDrag, currentIndex: number, previousContainer?: CdkDropListContainer): void;
drop(item: CdkDrag, currentIndex: number, previousContainer: CdkDropListContainer,
isPointerOverContainer: boolean): void;
/**
* Emits an event to indicate that the user moved an item into the container.
@@ -63,7 +66,7 @@ export interface CdkDropListContainer<T = any> {
_draggables: QueryList<CdkDrag>;
_getSiblingContainerFromPosition(item: CdkDrag, x: number, y: number):
CdkDropListContainer | null;
_canReturnItem(x: number, y: number): boolean;
_isOverContainer(x: number, y: number): boolean;
}
/**
+10 -8
View File
@@ -210,16 +210,19 @@ export class CdkDropList<T = any> implements OnInit, OnDestroy {
* @param item Item being dropped into the container.
* @param currentIndex Index at which the item should be inserted.
* @param previousContainer Container from which the item got dragged in.
* @param isPointerOverContainer Whether the user's pointer was over the
* container when the item was dropped.
*/
drop(item: CdkDrag, currentIndex: number, previousContainer: CdkDropList): void {
drop(item: CdkDrag, currentIndex: number, previousContainer: CdkDropList,
isPointerOverContainer: boolean): void {
this._reset();
this.dropped.emit({
item,
currentIndex,
previousIndex: previousContainer.getItemIndex(item),
container: this,
// TODO(crisbeto): reconsider whether to make this null if the containers are the same.
previousContainer
previousContainer,
isPointerOverContainer
});
}
@@ -388,12 +391,11 @@ export class CdkDropList<T = any> implements OnInit, OnDestroy {
}
/**
* Checks whether an item that started in this container can be returned to it,
* after it was moved out into another container.
* @param x Position of the item along the X axis.
* @param y Position of the item along the Y axis.
* Checks whether the user's pointer is positioned over the container.
* @param x Pointer position along the X axis.
* @param y Pointer position along the Y axis.
*/
_canReturnItem(x: number, y: number): boolean {
_isOverContainer(x: number, y: number): boolean {
return isInsideClientRect(this._positionCache.self, x, y);
}
+5 -4
View File
@@ -41,6 +41,7 @@ export interface CdkDragConfig {
export interface CdkDragDrop<T, O = T> {
container: CdkDropListContainer<T>;
currentIndex: number;
isPointerOverContainer: boolean;
item: CdkDrag;
previousContainer: CdkDropListContainer<O>;
previousIndex: number;
@@ -119,13 +120,13 @@ export declare class CdkDropList<T = any> implements OnInit, OnDestroy {
orientation: 'horizontal' | 'vertical';
sorted: EventEmitter<CdkDragSortEvent<T>>;
constructor(element: ElementRef<HTMLElement>, _dragDropRegistry: DragDropRegistry<CdkDrag, CdkDropList<T>>, _changeDetectorRef: ChangeDetectorRef, _dir?: Directionality | undefined, _group?: CdkDropListGroup<CdkDropList<any>> | undefined);
_canReturnItem(x: number, y: number): boolean;
_getSiblingContainerFromPosition(item: CdkDrag, x: number, y: number): CdkDropList | null;
_isOverContainer(x: number, y: number): boolean;
_sortItem(item: CdkDrag, pointerX: number, pointerY: number, pointerDelta: {
x: number;
y: number;
}): void;
drop(item: CdkDrag, currentIndex: number, previousContainer: CdkDropList): void;
drop(item: CdkDrag, currentIndex: number, previousContainer: CdkDropList, isPointerOverContainer: boolean): void;
enter(item: CdkDrag, pointerX: number, pointerY: number): void;
exit(item: CdkDrag): void;
getItemIndex(item: CdkDrag): number;
@@ -142,13 +143,13 @@ export interface CdkDropListContainer<T = any> {
id: string;
lockAxis: 'x' | 'y';
orientation: 'horizontal' | 'vertical';
_canReturnItem(x: number, y: number): boolean;
_getSiblingContainerFromPosition(item: CdkDrag, x: number, y: number): CdkDropListContainer | null;
_isOverContainer(x: number, y: number): boolean;
_sortItem(item: CdkDrag, pointerX: number, pointerY: number, delta: {
x: number;
y: number;
}): void;
drop(item: CdkDrag, currentIndex: number, previousContainer?: CdkDropListContainer): void;
drop(item: CdkDrag, currentIndex: number, previousContainer: CdkDropListContainer, isPointerOverContainer: boolean): void;
enter(item: CdkDrag, pointerX: number, pointerY: number): void;
exit(item: CdkDrag): void;
getItemIndex(item: CdkDrag): number;