Skip to content

Commit 0bf2d82

Browse files
crisbetojosephperrott
authored andcommitted
feat(drag-drop): add support for sorting animations (#12530)
1 parent 51b3727 commit 0bf2d82

File tree

5 files changed

+204
-87
lines changed

5 files changed

+204
-87
lines changed

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

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,7 @@ describe('CdkDrag', () => {
562562
// Add a few pixels to the left offset so we get some overlap.
563563
dispatchMouseEvent(document, 'mousemove', elementRect.left + 5, elementRect.top);
564564
fixture.detectChanges();
565-
expect(getElementIndex(placeholder)).toBe(i);
565+
expect(getElementIndexByPosition(placeholder, 'left')).toBe(i);
566566
}
567567

568568
dispatchMouseEvent(document, 'mouseup');
@@ -590,7 +590,7 @@ describe('CdkDrag', () => {
590590
// Remove a few pixels from the right offset so we get some overlap.
591591
dispatchMouseEvent(document, 'mousemove', elementRect.right - 5, elementRect.top);
592592
fixture.detectChanges();
593-
expect(getElementIndex(placeholder)).toBe(Math.min(i + 1, items.length - 1));
593+
expect(getElementIndexByPosition(placeholder, 'left')).toBe(i);
594594
}
595595

596596
dispatchMouseEvent(document, 'mouseup');
@@ -660,6 +660,31 @@ describe('CdkDrag', () => {
660660
expect(placeholder.textContent!.trim()).toContain('Custom placeholder');
661661
}));
662662

663+
it('should clear the `transform` value from siblings when item is dropped`', fakeAsync(() => {
664+
const fixture = createComponent(DraggableInDropZone);
665+
fixture.detectChanges();
666+
667+
const dragItems = fixture.componentInstance.dragItems;
668+
const firstItem = dragItems.first;
669+
const thirdItem = dragItems.toArray()[2].element.nativeElement;
670+
const thirdItemRect = thirdItem.getBoundingClientRect();
671+
672+
dispatchMouseEvent(firstItem.element.nativeElement, 'mousedown');
673+
fixture.detectChanges();
674+
675+
dispatchMouseEvent(document, 'mousemove', thirdItemRect.left + 1, thirdItemRect.top + 1);
676+
fixture.detectChanges();
677+
678+
expect(thirdItem.style.transform).toBeTruthy();
679+
680+
dispatchMouseEvent(document, 'mouseup');
681+
fixture.detectChanges();
682+
flush();
683+
fixture.detectChanges();
684+
685+
expect(thirdItem.style.transform).toBeFalsy();
686+
}));
687+
663688
});
664689

665690
describe('in a connected drop container', () => {
@@ -1151,9 +1176,15 @@ function dragElementViaTouch(fixture: ComponentFixture<any>,
11511176
fixture.detectChanges();
11521177
}
11531178

1154-
/** Gets the index of a DOM element inside its parent. */
1155-
function getElementIndex(element: HTMLElement) {
1156-
return element.parentElement ? Array.from(element.parentElement.children).indexOf(element) : -1;
1179+
/** Gets the index of an element among its siblings, based on their position on the page. */
1180+
function getElementIndexByPosition(element: HTMLElement, direction: 'top' | 'left') {
1181+
if (!element.parentElement) {
1182+
return -1;
1183+
}
1184+
1185+
return Array.from(element.parentElement.children)
1186+
.sort((a, b) => a.getBoundingClientRect()[direction] - b.getBoundingClientRect()[direction])
1187+
.indexOf(element);
11571188
}
11581189

11591190
/**
@@ -1193,7 +1224,7 @@ function assertDownwardSorting(fixture: ComponentFixture<any>, items: Element[])
11931224
// Add a few pixels to the top offset so we get some overlap.
11941225
dispatchMouseEvent(document, 'mousemove', elementRect.left, elementRect.top + 5);
11951226
fixture.detectChanges();
1196-
expect(getElementIndex(placeholder)).toBe(i);
1227+
expect(getElementIndexByPosition(placeholder, 'top')).toBe(i);
11971228
}
11981229

11991230
dispatchMouseEvent(document, 'mouseup');
@@ -1222,7 +1253,7 @@ function assertUpwardSorting(fixture: ComponentFixture<any>, items: Element[]) {
12221253
// Remove a few pixels from the bottom offset so we get some overlap.
12231254
dispatchMouseEvent(document, 'mousemove', elementRect.left, elementRect.bottom - 5);
12241255
fixture.detectChanges();
1225-
expect(getElementIndex(placeholder)).toBe(Math.min(i + 1, items.length - 1));
1256+
expect(getElementIndexByPosition(placeholder, 'top')).toBe(i);
12261257
}
12271258

12281259
dispatchMouseEvent(document, 'mouseup');

src/cdk-experimental/drag-drop/drag.ts

Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ import {CdkDragDropRegistry} from './drag-drop-registry';
3535
import {Subject, merge} from 'rxjs';
3636
import {takeUntil} from 'rxjs/operators';
3737

38-
// TODO: add auto-scrolling functionality.
39-
// TODO: add an API for moving a draggable up/down the
38+
// TODO(crisbeto): add auto-scrolling functionality.
39+
// TODO(crisbeto): add an API for moving a draggable up/down the
4040
// list programmatically. Useful for keyboard controls.
4141

4242
/** Element that can be moved inside a CdkDrop container. */
@@ -236,7 +236,7 @@ export class CdkDrag<T = any> implements OnDestroy {
236236

237237
/** Handler that is invoked when the user moves their pointer after they've initiated a drag. */
238238
private _pointerMove = (event: MouseEvent | TouchEvent) => {
239-
// TODO: this should start dragging after a certain threshold,
239+
// TODO(crisbeto): this should start dragging after a certain threshold,
240240
// otherwise we risk interfering with clicks on the element.
241241
if (!this._dragDropRegistry.isDragging(this)) {
242242
return;
@@ -279,8 +279,6 @@ export class CdkDrag<T = any> implements OnDestroy {
279279

280280
/** Cleans up the DOM artifacts that were added to facilitate the element being dragged. */
281281
private _cleanupDragArtifacts() {
282-
const currentIndex = this._getElementIndexInDom(this._placeholder);
283-
284282
// Restore the element's visibility and insert it at its old position in the DOM.
285283
// It's important that we maintain the position, because moving the element around in the DOM
286284
// can throw off `NgFor` which does smart diffing and re-creates elements only when necessary,
@@ -298,6 +296,8 @@ export class CdkDrag<T = any> implements OnDestroy {
298296

299297
// Re-enter the NgZone since we bound `document` events on the outside.
300298
this._ngZone.run(() => {
299+
const currentIndex = this.dropContainer.getItemIndex(this);
300+
301301
this.ended.emit({source: this});
302302
this.dropped.emit({
303303
item: this,
@@ -328,7 +328,7 @@ export class CdkDrag<T = any> implements OnDestroy {
328328
// Notify the new container that the item has entered.
329329
this.entered.emit({ item: this, container: newContainer });
330330
this.dropContainer = newContainer;
331-
this.dropContainer.enter(this);
331+
this.dropContainer.enter(this, x, y);
332332
});
333333
}
334334

@@ -386,37 +386,6 @@ export class CdkDrag<T = any> implements OnDestroy {
386386
return placeholder;
387387
}
388388

389-
/** Gets the index of an element, based on its index in the DOM. */
390-
private _getElementIndexInDom(element: HTMLElement): number {
391-
// Note: we may be able to figure this in memory while sorting, but doing so won't be very
392-
// reliable when transferring between containers, because the new container doesn't have
393-
// the proper indices yet. Also this will work better for the case where the consumer
394-
// isn't using an `ngFor` to render the list.
395-
if (!element.parentElement) {
396-
return -1;
397-
}
398-
399-
// Avoid accessing `children` and `children.length` too much since they're a "live collection".
400-
let index = 0;
401-
const siblings = element.parentElement.children;
402-
const siblingsLength = siblings.length;
403-
const draggableElements = this.dropContainer._draggables
404-
.filter(item => item !== this)
405-
.map(item => item.element.nativeElement);
406-
407-
// Loop through the sibling elements to find out the index of the
408-
// current one, while skipping any elements that aren't draggable.
409-
for (let i = 0; i < siblingsLength; i++) {
410-
if (siblings[i] === element) {
411-
return index;
412-
} else if (draggableElements.indexOf(siblings[i] as HTMLElement) > -1) {
413-
index++;
414-
}
415-
}
416-
417-
return -1;
418-
}
419-
420389
/**
421390
* Figures out the coordinates at which an element was picked up.
422391
* @param referenceElement Element that initiated the dragging.

src/cdk-experimental/drag-drop/drop-container.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ export interface CdkDropContainer<T = any> {
3030
/**
3131
* Emits an event to indicate that the user moved an item into the container.
3232
* @param item Item that was moved into the container.
33+
* @param xOffset Position of the item along the X axis.
34+
* @param yOffset Position of the item along the Y axis.
3335
*/
34-
enter(item: CdkDrag): void;
36+
enter(item: CdkDrag, xOffset: number, yOffset: number): void;
3537

3638
/**
3739
* Removes an item from the container after it was dragged into another container by the user.

0 commit comments

Comments
 (0)