Skip to content

feat(drag-drop): add the ability to disable sorting in a list #15064

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -2183,6 +2183,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 @@ -2794,6 +2847,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 @@ -116,6 +116,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 @@ -307,6 +315,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 @@ -127,6 +127,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 @@ -294,6 +295,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