Skip to content

Commit

Permalink
feat(drag-drop): add the ability to disable sorting in a list (#15064)
Browse files Browse the repository at this point in the history
Adds the ability to disable sorting inside of a `CdkDropList`. This allows for use cases where one list is considered the source and one or more connected lists are the destinations, and the user isn't supposed to sort the items within the source list.

Fixes #14838.
  • Loading branch information
crisbeto authored and jelbourn committed Feb 22, 2019
1 parent dd4257e commit 629460f
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 10 deletions.
101 changes: 101 additions & 0 deletions src/cdk/drag-drop/directives/drag.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2241,6 +2241,59 @@ describe('CdkDrag', () => {
.toEqual(['Zero', 'One', 'Two', 'Three']);
}));

it('should not sort an item if sorting the list is disabled', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZone);
fixture.detectChanges();

const dropInstance = fixture.componentInstance.dropInstance;
const dragItems = fixture.componentInstance.dragItems;

dropInstance.sortingDisabled = true;

expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
.toEqual(['Zero', 'One', 'Two', 'Three']);

const firstItem = dragItems.first;
const thirdItemRect = dragItems.toArray()[2].element.nativeElement.getBoundingClientRect();
const targetX = thirdItemRect.left + 1;
const targetY = thirdItemRect.top + 1;

startDraggingViaMouse(fixture, firstItem.element.nativeElement);

const placeholder = document.querySelector('.cdk-drag-placeholder') as HTMLElement;

dispatchMouseEvent(document, 'mousemove', targetX, targetY);
fixture.detectChanges();

expect(getElementIndexByPosition(placeholder, 'top'))
.toBe(0, 'Expected placeholder to stay in place.');

dispatchMouseEvent(document, 'mouseup', targetX, targetY);
fixture.detectChanges();

flush();
fixture.detectChanges();

expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1);

const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0];

// Assert the event like this, rather than `toHaveBeenCalledWith`, because Jasmine will
// go into an infinite loop trying to stringify the event, if the test fails.
expect(event).toEqual({
previousIndex: 0,
currentIndex: 0,
item: firstItem,
container: dropInstance,
previousContainer: dropInstance,
isPointerOverContainer: true
});

expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
.toEqual(['Zero', 'One', 'Two', 'Three']);
}));


});

describe('in a connected drop container', () => {
Expand Down Expand Up @@ -2852,6 +2905,54 @@ describe('CdkDrag', () => {

}));

it('should return the item to its initial position, if sorting in the source container ' +
'was disabled', fakeAsync(() => {
const fixture = createComponent(ConnectedDropZones);
fixture.detectChanges();

const groups = fixture.componentInstance.groupedDragItems;
const dropZones = fixture.componentInstance.dropInstances.map(d => d.element.nativeElement);
const item = groups[0][1];
const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect();

fixture.componentInstance.dropInstances.first.sortingDisabled = true;
startDraggingViaMouse(fixture, item.element.nativeElement);

const placeholder = dropZones[0].querySelector('.cdk-drag-placeholder')!;

expect(placeholder).toBeTruthy();
expect(dropZones[0].contains(placeholder))
.toBe(true, 'Expected placeholder to be inside the first container.');
expect(getElementIndexByPosition(placeholder, 'top'))
.toBe(1, 'Expected placeholder to be at item index.');

dispatchMouseEvent(document, 'mousemove', targetRect.left + 1, targetRect.top + 1);
fixture.detectChanges();

expect(dropZones[1].contains(placeholder))
.toBe(true, 'Expected placeholder to be inside second container.');
expect(getElementIndexByPosition(placeholder, 'top'))
.toBe(3, 'Expected placeholder to be at the target index.');

const firstInitialSiblingRect = groups[0][0].element
.nativeElement.getBoundingClientRect();

// Return the item to an index that is different from the initial one.
dispatchMouseEvent(document, 'mousemove', firstInitialSiblingRect.left + 1,
firstInitialSiblingRect.top + 1);
fixture.detectChanges();

expect(dropZones[0].contains(placeholder))
.toBe(true, 'Expected placeholder to be back inside first container.');
expect(getElementIndexByPosition(placeholder, 'top'))
.toBe(1, 'Expected placeholder to be back at the initial index.');

dispatchMouseEvent(document, 'mouseup');
fixture.detectChanges();

expect(fixture.componentInstance.droppedSpy).not.toHaveBeenCalled();
}));

});

});
Expand Down
9 changes: 9 additions & 0 deletions src/cdk/drag-drop/directives/drop-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ export class CdkDropList<T = any> implements CdkDropListContainer, AfterContentI
}
private _disabled = false;

/** Whether starting a dragging sequence from this container is disabled. */
@Input('cdkDropListSortingDisabled')
get sortingDisabled(): boolean { return this._sortingDisabled; }
set sortingDisabled(value: boolean) {
this._sortingDisabled = coerceBooleanProperty(value);
}
private _sortingDisabled = false;

/**
* Function that is used to determine whether an item
* is allowed to be moved into a drop container.
Expand Down Expand Up @@ -308,6 +316,7 @@ export class CdkDropList<T = any> implements CdkDropListContainer, AfterContentI
}

ref.lockAxis = this.lockAxis;
ref.sortingDisabled = this.sortingDisabled;
ref
.connectedTo(siblings.filter(drop => drop && drop !== this).map(list => list._dropListRef))
.withOrientation(this.orientation);
Expand Down
38 changes: 28 additions & 10 deletions src/cdk/drag-drop/drop-list-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ export class DropListRef<T = any> {
/** Whether starting a dragging sequence from this container is disabled. */
disabled: boolean = false;

/** Whether sorting items within the list is disabled. */
sortingDisabled: boolean = true;

/** Locks the position of the draggable elements inside the container along the specified axis. */
lockAxis: 'x' | 'y';

Expand Down Expand Up @@ -189,28 +192,43 @@ export class DropListRef<T = any> {
this.entered.next({item, container: this});
this.start();

// We use the coordinates of where the item entered the drop
// zone to figure out at which index it should be inserted.
const newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY);
const currentIndex = this._activeDraggables.indexOf(item);
const newPositionReference = this._activeDraggables[newIndex];
// If sorting is disabled, we want the item to return to its starting
// position if the user is returning it to its initial container.
let newIndex = this.sortingDisabled ? this._draggables.indexOf(item) : -1;

if (newIndex === -1) {
// We use the coordinates of where the item entered the drop
// zone to figure out at which index it should be inserted.
newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY);
}

const activeDraggables = this._activeDraggables;
const currentIndex = activeDraggables.indexOf(item);
const placeholder = item.getPlaceholderElement();
let newPositionReference: DragRef | undefined = activeDraggables[newIndex];

// If the item at the new position is the same as the item that is being dragged,
// it means that we're trying to restore the item to its initial position. In this
// case we should use the next item from the list as the reference.
if (newPositionReference === item) {
newPositionReference = activeDraggables[newIndex + 1];
}

// Since the item may be in the `activeDraggables` already (e.g. if the user dragged it
// into another container and back again), we have to ensure that it isn't duplicated.
if (currentIndex > -1) {
this._activeDraggables.splice(currentIndex, 1);
activeDraggables.splice(currentIndex, 1);
}

// Don't use items that are being dragged as a reference, because
// their element has been moved down to the bottom of the body.
if (newPositionReference && !this._dragDropRegistry.isDragging(newPositionReference)) {
const element = newPositionReference.getRootElement();
element.parentElement!.insertBefore(placeholder, element);
this._activeDraggables.splice(newIndex, 0, item);
activeDraggables.splice(newIndex, 0, item);
} else {
this.element.appendChild(placeholder);
this._activeDraggables.push(item);
activeDraggables.push(item);
}

// The transform needs to be cleared so it doesn't throw off the measurements.
Expand Down Expand Up @@ -321,8 +339,8 @@ export class DropListRef<T = any> {
*/
_sortItem(item: DragRef, pointerX: number, pointerY: number,
pointerDelta: {x: number, y: number}): void {
// Don't sort the item if it's out of range.
if (!this._isPointerNearDropContainer(pointerX, pointerY)) {
// Don't sort the item if sorting is disabled or it's out of range.
if (this.sortingDisabled || !this._isPointerNearDropContainer(pointerX, pointerY)) {
return;
}

Expand Down
2 changes: 2 additions & 0 deletions tools/public_api_guard/cdk/drag-drop.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export declare class CdkDropList<T = any> implements CdkDropListContainer, After
lockAxis: 'x' | 'y';
orientation: 'horizontal' | 'vertical';
sorted: EventEmitter<CdkDragSortEvent<T>>;
sortingDisabled: boolean;
constructor(
element: ElementRef<HTMLElement>, dragDropRegistry: DragDropRegistry<DragRef, DropListRef>, _changeDetectorRef: ChangeDetectorRef, _dir?: Directionality | undefined, _group?: CdkDropListGroup<CdkDropList<any>> | undefined, _document?: any,
dragDrop?: DragDrop);
Expand Down Expand Up @@ -296,6 +297,7 @@ export declare class DropListRef<T = any> {
container: DropListRef<any>;
item: DragRef;
}>;
sortingDisabled: boolean;
constructor(element: ElementRef<HTMLElement> | HTMLElement, _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>, _document: any);
_canReceive(item: DragRef, x: number, y: number): boolean;
_getSiblingContainerFromPosition(item: DragRef, x: number, y: number): DropListRef | undefined;
Expand Down

0 comments on commit 629460f

Please sign in to comment.