mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
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:
committed by
Jeremy Elbourn
parent
acaed95b0d
commit
42de6a2c98
@@ -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. */
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user