Skip to content

Commit 2865ac8

Browse files
committed
feat(drag-drop): add the ability to disable sorting in a list
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.
1 parent 5846038 commit 2865ac8

File tree

4 files changed

+140
-10
lines changed

4 files changed

+140
-10
lines changed

src/cdk/drag-drop/directives/drag.spec.ts

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2183,6 +2183,59 @@ describe('CdkDrag', () => {
21832183
.toEqual(['Zero', 'One', 'Two', 'Three']);
21842184
}));
21852185

2186+
it('should not sort an item if sorting the list is disabled', fakeAsync(() => {
2187+
const fixture = createComponent(DraggableInDropZone);
2188+
fixture.detectChanges();
2189+
2190+
const dropInstance = fixture.componentInstance.dropInstance;
2191+
const dragItems = fixture.componentInstance.dragItems;
2192+
2193+
dropInstance.sortingDisabled = true;
2194+
2195+
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
2196+
.toEqual(['Zero', 'One', 'Two', 'Three']);
2197+
2198+
const firstItem = dragItems.first;
2199+
const thirdItemRect = dragItems.toArray()[2].element.nativeElement.getBoundingClientRect();
2200+
const targetX = thirdItemRect.left + 1;
2201+
const targetY = thirdItemRect.top + 1;
2202+
2203+
startDraggingViaMouse(fixture, firstItem.element.nativeElement);
2204+
2205+
const placeholder = document.querySelector('.cdk-drag-placeholder') as HTMLElement;
2206+
2207+
dispatchMouseEvent(document, 'mousemove', targetX, targetY);
2208+
fixture.detectChanges();
2209+
2210+
expect(getElementIndexByPosition(placeholder, 'top'))
2211+
.toBe(0, 'Expected placeholder to stay in place.');
2212+
2213+
dispatchMouseEvent(document, 'mouseup', targetX, targetY);
2214+
fixture.detectChanges();
2215+
2216+
flush();
2217+
fixture.detectChanges();
2218+
2219+
expect(fixture.componentInstance.droppedSpy).toHaveBeenCalledTimes(1);
2220+
2221+
const event = fixture.componentInstance.droppedSpy.calls.mostRecent().args[0];
2222+
2223+
// Assert the event like this, rather than `toHaveBeenCalledWith`, because Jasmine will
2224+
// go into an infinite loop trying to stringify the event, if the test fails.
2225+
expect(event).toEqual({
2226+
previousIndex: 0,
2227+
currentIndex: 0,
2228+
item: firstItem,
2229+
container: dropInstance,
2230+
previousContainer: dropInstance,
2231+
isPointerOverContainer: true
2232+
});
2233+
2234+
expect(dragItems.map(drag => drag.element.nativeElement.textContent!.trim()))
2235+
.toEqual(['Zero', 'One', 'Two', 'Three']);
2236+
}));
2237+
2238+
21862239
});
21872240

21882241
describe('in a connected drop container', () => {
@@ -2794,6 +2847,54 @@ describe('CdkDrag', () => {
27942847

27952848
}));
27962849

2850+
it('should return the item to its initial position, if sorting in the source container ' +
2851+
'was disabled', fakeAsync(() => {
2852+
const fixture = createComponent(ConnectedDropZones);
2853+
fixture.detectChanges();
2854+
2855+
const groups = fixture.componentInstance.groupedDragItems;
2856+
const dropZones = fixture.componentInstance.dropInstances.map(d => d.element.nativeElement);
2857+
const item = groups[0][1];
2858+
const targetRect = groups[1][2].element.nativeElement.getBoundingClientRect();
2859+
2860+
fixture.componentInstance.dropInstances.first.sortingDisabled = true;
2861+
startDraggingViaMouse(fixture, item.element.nativeElement);
2862+
2863+
const placeholder = dropZones[0].querySelector('.cdk-drag-placeholder')!;
2864+
2865+
expect(placeholder).toBeTruthy();
2866+
expect(dropZones[0].contains(placeholder))
2867+
.toBe(true, 'Expected placeholder to be inside the first container.');
2868+
expect(getElementIndexByPosition(placeholder, 'top'))
2869+
.toBe(1, 'Expected placeholder to be at item index.');
2870+
2871+
dispatchMouseEvent(document, 'mousemove', targetRect.left + 1, targetRect.top + 1);
2872+
fixture.detectChanges();
2873+
2874+
expect(dropZones[1].contains(placeholder))
2875+
.toBe(true, 'Expected placeholder to be inside second container.');
2876+
expect(getElementIndexByPosition(placeholder, 'top'))
2877+
.toBe(3, 'Expected placeholder to be at the target index.');
2878+
2879+
const firstInitialSiblingRect = groups[0][0].element
2880+
.nativeElement.getBoundingClientRect();
2881+
2882+
// Return the item to an index that is different from the initial one.
2883+
dispatchMouseEvent(document, 'mousemove', firstInitialSiblingRect.left + 1,
2884+
firstInitialSiblingRect.top + 1);
2885+
fixture.detectChanges();
2886+
2887+
expect(dropZones[0].contains(placeholder))
2888+
.toBe(true, 'Expected placeholder to be back inside first container.');
2889+
expect(getElementIndexByPosition(placeholder, 'top'))
2890+
.toBe(1, 'Expected placeholder to be back at the initial index.');
2891+
2892+
dispatchMouseEvent(document, 'mouseup');
2893+
fixture.detectChanges();
2894+
2895+
expect(fixture.componentInstance.droppedSpy).not.toHaveBeenCalled();
2896+
}));
2897+
27972898
});
27982899

27992900
});

src/cdk/drag-drop/directives/drop-list.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,14 @@ export class CdkDropList<T = any> implements CdkDropListContainer, AfterContentI
116116
}
117117
private _disabled = false;
118118

119+
/** Whether starting a dragging sequence from this container is disabled. */
120+
@Input('cdkDropListSortingDisabled')
121+
get sortingDisabled(): boolean { return this._sortingDisabled; }
122+
set sortingDisabled(value: boolean) {
123+
this._sortingDisabled = coerceBooleanProperty(value);
124+
}
125+
private _sortingDisabled = false;
126+
119127
/**
120128
* Function that is used to determine whether an item
121129
* is allowed to be moved into a drop container.
@@ -307,6 +315,7 @@ export class CdkDropList<T = any> implements CdkDropListContainer, AfterContentI
307315
}
308316

309317
ref.lockAxis = this.lockAxis;
318+
ref.sortingDisabled = this.sortingDisabled;
310319
ref
311320
.connectedTo(siblings.filter(drop => drop && drop !== this).map(list => list._dropListRef))
312321
.withOrientation(this.orientation);

src/cdk/drag-drop/drop-list-ref.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ export class DropListRef<T = any> {
6363
/** Whether starting a dragging sequence from this container is disabled. */
6464
disabled: boolean = false;
6565

66+
/** Whether sorting items within the list is disabled. */
67+
sortingDisabled: boolean = true;
68+
6669
/** Locks the position of the draggable elements inside the container along the specified axis. */
6770
lockAxis: 'x' | 'y';
6871

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

192-
// We use the coordinates of where the item entered the drop
193-
// zone to figure out at which index it should be inserted.
194-
const newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY);
195-
const currentIndex = this._activeDraggables.indexOf(item);
196-
const newPositionReference = this._activeDraggables[newIndex];
195+
// If sorting is disabled, we want the item to return to its starting
196+
// position, if the user is returning it to its initial container.
197+
let newIndex = this.sortingDisabled ? this._draggables.indexOf(item) : -1;
198+
199+
if (newIndex === -1) {
200+
// We use the coordinates of where the item entered the drop
201+
// zone to figure out at which index it should be inserted.
202+
newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY);
203+
}
204+
205+
const activeDraggables = this._activeDraggables;
206+
const currentIndex = activeDraggables.indexOf(item);
197207
const placeholder = item.getPlaceholderElement();
208+
let newPositionReference: DragRef | undefined = activeDraggables[newIndex];
209+
210+
// If the item at the new position is the same as the item that is being dragged,
211+
// it means that we're trying to restore the item to its initial position. In this
212+
// case we should use the next item from the list as the reference.
213+
if (newPositionReference === item) {
214+
newPositionReference = activeDraggables[newIndex + 1];
215+
}
198216

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

205223
// Don't use items that are being dragged as a reference, because
206224
// their element has been moved down to the bottom of the body.
207225
if (newPositionReference && !this._dragDropRegistry.isDragging(newPositionReference)) {
208226
const element = newPositionReference.getRootElement();
209227
element.parentElement!.insertBefore(placeholder, element);
210-
this._activeDraggables.splice(newIndex, 0, item);
228+
activeDraggables.splice(newIndex, 0, item);
211229
} else {
212230
this.element.appendChild(placeholder);
213-
this._activeDraggables.push(item);
231+
activeDraggables.push(item);
214232
}
215233

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

tools/public_api_guard/cdk/drag-drop.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ export declare class CdkDropList<T = any> implements CdkDropListContainer, After
127127
lockAxis: 'x' | 'y';
128128
orientation: 'horizontal' | 'vertical';
129129
sorted: EventEmitter<CdkDragSortEvent<T>>;
130+
sortingDisabled: boolean;
130131
constructor(
131132
element: ElementRef<HTMLElement>, dragDropRegistry: DragDropRegistry<DragRef, DropListRef>, _changeDetectorRef: ChangeDetectorRef, _dir?: Directionality | undefined, _group?: CdkDropListGroup<CdkDropList<any>> | undefined, _document?: any,
132133
dragDrop?: DragDrop);
@@ -294,6 +295,7 @@ export declare class DropListRef<T = any> {
294295
container: DropListRef<any>;
295296
item: DragRef;
296297
}>;
298+
sortingDisabled: boolean;
297299
constructor(element: ElementRef<HTMLElement> | HTMLElement, _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>, _document: any);
298300
_canReceive(item: DragRef, x: number, y: number): boolean;
299301
_getSiblingContainerFromPosition(item: DragRef, x: number, y: number): DropListRef | undefined;

0 commit comments

Comments
 (0)