feat(drag-drop): add function to determine whether an item is allowed to be transferred (#12673)

Adds the `enterPredicate` function which allows consumers to determine whether a particular item is allowed to be moved into a drop container. This is more granular than excluding entire groups of items using `connectedTo`.
This commit is contained in:
Kristiyan Kostadinov
2018-08-15 23:41:54 +02:00
committed by mmalerba
parent 03527c632b
commit 91b171cd13
4 changed files with 43 additions and 8 deletions
+31 -3
View File
@@ -942,6 +942,34 @@ describe('CdkDrag', () => {
});
}));
it('should not be able to transfer an item that does not match the `enterPredicate`',
fakeAsync(() => {
const fixture = createComponent(ConnectedDropZones);
fixture.detectChanges();
fixture.componentInstance.dropInstances.forEach(d => d.enterPredicate = () => false);
fixture.detectChanges();
const groups = fixture.componentInstance.groupedDragItems.slice();
const element = groups[0][1].element.nativeElement;
const dropInstances = fixture.componentInstance.dropInstances.toArray();
const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect();
dragElementViaMouse(fixture, element, targetRect.left + 1, targetRect.top + 1);
flush();
fixture.detectChanges();
const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0];
expect(event).toBeTruthy();
expect(event).toEqual({
previousIndex: 1,
currentIndex: 1,
item: groups[0][1],
container: dropInstances[0],
previousContainer: dropInstances[0]
});
}));
it('should be able to start dragging after an item has been transferred', fakeAsync(() => {
const fixture = createComponent(ConnectedDropZones);
@@ -1129,7 +1157,7 @@ export class StandaloneDraggableWithMultipleHandles {
<div
*ngFor="let item of items"
cdkDrag
[data]="item"
[cdkDragData]="item"
style="width: 100%; height: ${ITEM_HEIGHT}px; background: red;">{{item}}</div>
</cdk-drop>
`
@@ -1239,7 +1267,7 @@ export class DraggableInDropZoneWithCustomPlaceholder {
[data]="todo"
[connectedTo]="[doneZone]"
(dropped)="droppedSpy($event)">
<div *ngFor="let item of todo" cdkDrag>{{item}}</div>
<div [cdkDragData]="item" *ngFor="let item of todo" cdkDrag>{{item}}</div>
</cdk-drop>
<cdk-drop
@@ -1247,7 +1275,7 @@ export class DraggableInDropZoneWithCustomPlaceholder {
[data]="done"
[connectedTo]="[todoZone]"
(dropped)="droppedSpy($event)">
<div *ngFor="let item of done" cdkDrag>{{item}}</div>
<div [cdkDragData]="item" *ngFor="let item of done" cdkDrag>{{item}}</div>
</cdk-drop>
`
})
+2 -2
View File
@@ -123,7 +123,7 @@ export class CdkDrag<T = any> implements OnDestroy {
@ContentChild(CdkDragPlaceholder) _placeholderTemplate: CdkDragPlaceholder;
/** Arbitrary data to attach to this drag instance. */
@Input() data: T;
@Input('cdkDragData') data: T;
/** Locks the position of the dragged element along the specified axis. */
@Input('cdkDragLockAxis') lockAxis: 'x' | 'y';
@@ -364,7 +364,7 @@ export class CdkDrag<T = any> implements OnDestroy {
*/
private _updateActiveDropContainer({x, y}) {
// Drop container that draggable has been moved into.
const newContainer = this.dropContainer._getSiblingContainerFromPosition(x, y);
const newContainer = this.dropContainer._getSiblingContainerFromPosition(this, x, y);
if (newContainer) {
this._ngZone.run(() => {
@@ -54,7 +54,7 @@ export interface CdkDropContainer<T = any> {
getItemIndex(item: CdkDrag): number;
_sortItem(item: CdkDrag, xOffset: number, yOffset: number): void;
_draggables: QueryList<CdkDrag>;
_getSiblingContainerFromPosition(x: number, y: number): CdkDropContainer | null;
_getSiblingContainerFromPosition(item: CdkDrag, x: number, y: number): CdkDropContainer | null;
}
/**
+9 -2
View File
@@ -72,6 +72,12 @@ export class CdkDrop<T = any> implements OnInit, OnDestroy {
/** Locks the position of the draggable elements inside the container along the specified axis. */
@Input() lockAxis: 'x' | 'y';
/**
* Function that is used to determine whether an item
* is allowed to be moved into a drop container.
*/
@Input() enterPredicate: (drag?: CdkDrag, drop?: CdkDrop) => boolean = () => true;
/** Emits when the user drops an item inside the container. */
@Output() dropped: EventEmitter<CdkDragDrop<T, any>> = new EventEmitter<CdkDragDrop<T, any>>();
@@ -252,16 +258,17 @@ export class CdkDrop<T = any> implements OnInit, OnDestroy {
/**
* Figures out whether an item should be moved into a sibling
* drop container, based on its current position.
* @param item Drag item that is being moved.
* @param x Position of the item along the X axis.
* @param y Position of the item along the Y axis.
*/
_getSiblingContainerFromPosition(x: number, y: number): CdkDrop | null {
_getSiblingContainerFromPosition(item: CdkDrag, x: number, y: number): CdkDrop | null {
const result = this._positionCache.siblings.find(({clientRect}) => {
const {top, bottom, left, right} = clientRect;
return y >= top && y <= bottom && x >= left && x <= right;
});
return result ? result.drop : null;
return result && result.drop.enterPredicate(item, this) ? result.drop : null;
}
/** Refreshes the position cache of the items and sibling containers. */