diff --git a/src/cdk/drag-drop/BUILD.bazel b/src/cdk/drag-drop/BUILD.bazel
index 3945ed74391a..1285650897d1 100644
--- a/src/cdk/drag-drop/BUILD.bazel
+++ b/src/cdk/drag-drop/BUILD.bazel
@@ -35,6 +35,7 @@ ng_test_library(
deps = [
":drag-drop",
"//src/cdk/bidi",
+ "//src/cdk/scrolling",
"//src/cdk/testing",
"@npm//@angular/common",
"@npm//rxjs",
diff --git a/src/cdk/drag-drop/directives/drag.spec.ts b/src/cdk/drag-drop/directives/drag.spec.ts
index 885120435701..205bc35ab6ca 100644
--- a/src/cdk/drag-drop/directives/drag.spec.ts
+++ b/src/cdk/drag-drop/directives/drag.spec.ts
@@ -5,6 +5,7 @@ import {
dispatchEvent,
dispatchMouseEvent,
dispatchTouchEvent,
+ dispatchFakeEvent,
} from '@angular/cdk/testing';
import {
AfterViewInit,
@@ -22,6 +23,7 @@ import {
} from '@angular/core';
import {TestBed, ComponentFixture, fakeAsync, flush, tick} from '@angular/core/testing';
import {DOCUMENT} from '@angular/common';
+import {ViewportRuler} from '@angular/cdk/scrolling';
import {of as observableOf} from 'rxjs';
import {DragDropModule} from '../drag-drop-module';
@@ -1478,6 +1480,96 @@ describe('CdkDrag', () => {
.toEqual(['Zero', 'One', 'Two', 'Three']);
}));
+ it('should calculate the index if the list is scrolled while dragging', fakeAsync(() => {
+ const fixture = createComponent(DraggableInScrollableVerticalDropZone);
+ fixture.detectChanges();
+ const dragItems = fixture.componentInstance.dragItems;
+ const firstItem = dragItems.first;
+ const thirdItemRect = dragItems.toArray()[2].element.nativeElement.getBoundingClientRect();
+ const list = fixture.componentInstance.dropInstance.element.nativeElement;
+
+ startDraggingViaMouse(fixture, firstItem.element.nativeElement);
+ fixture.detectChanges();
+
+ dispatchMouseEvent(document, 'mousemove', thirdItemRect.left + 1, thirdItemRect.top + 1);
+ fixture.detectChanges();
+
+ list.scrollTop = ITEM_HEIGHT * 10;
+ dispatchFakeEvent(list, 'scroll');
+ fixture.detectChanges();
+
+ dispatchMouseEvent(document, 'mouseup');
+ 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: 12,
+ item: firstItem,
+ container: fixture.componentInstance.dropInstance,
+ previousContainer: fixture.componentInstance.dropInstance,
+ isPointerOverContainer: jasmine.any(Boolean),
+ distance: {x: jasmine.any(Number), y: jasmine.any(Number)}
+ });
+ }));
+
+ it('should calculate the index if the viewport is scrolled while dragging', fakeAsync(() => {
+ const fixture = createComponent(DraggableInDropZone);
+
+ for (let i = 0; i < 200; i++) {
+ fixture.componentInstance.items.push({
+ value: `Extra item ${i}`,
+ height: ITEM_HEIGHT,
+ margin: 0
+ });
+ }
+
+ fixture.detectChanges();
+ const dragItems = fixture.componentInstance.dragItems;
+ const firstItem = dragItems.first;
+ const thirdItemRect = dragItems.toArray()[2].element.nativeElement.getBoundingClientRect();
+
+ startDraggingViaMouse(fixture, firstItem.element.nativeElement);
+ fixture.detectChanges();
+
+ dispatchMouseEvent(document, 'mousemove', thirdItemRect.left + 1, thirdItemRect.top + 1);
+ fixture.detectChanges();
+
+ scrollTo(0, ITEM_HEIGHT * 10);
+ dispatchFakeEvent(document, 'scroll');
+ fixture.detectChanges();
+
+ dispatchMouseEvent(document, 'mouseup');
+ 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: 12,
+ item: firstItem,
+ container: fixture.componentInstance.dropInstance,
+ previousContainer: fixture.componentInstance.dropInstance,
+ isPointerOverContainer: jasmine.any(Boolean),
+ distance: {x: jasmine.any(Number), y: jasmine.any(Number)}
+ });
+
+ scrollTo(0, 0);
+ }));
+
it('should create a preview element while the item is dragged', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZone);
fixture.detectChanges();
@@ -2374,6 +2466,29 @@ describe('CdkDrag', () => {
expect(preview.style.transform).toBe('translate3d(50px, 50px, 0px)');
}));
+ it('should keep the preview next to the trigger if the page was scrolled', fakeAsync(() => {
+ const fixture = createComponent(DraggableInDropZoneWithCustomPreview);
+ fixture.detectChanges();
+ const cleanup = makePageScrollable();
+ const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement;
+
+ startDraggingViaMouse(fixture, item, 50, 50);
+
+ const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement;
+ expect(preview.style.transform).toBe('translate3d(50px, 50px, 0px)');
+
+ scrollTo(0, 500);
+ fixture.detectChanges();
+
+ // Move the pointer a bit so the preview has to reposition.
+ dispatchMouseEvent(document, 'mousemove', 55, 55);
+ fixture.detectChanges();
+
+ expect(preview.style.transform).toBe('translate3d(55px, 555px, 0px)');
+
+ cleanup();
+ }));
+
it('should lock position inside a drop container along the x axis', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZoneWithCustomPreview);
fixture.detectChanges();
@@ -2656,6 +2771,272 @@ describe('CdkDrag', () => {
.toBe(1, 'Expected only one item to continue to be dragged.');
}));
+ it('should should be able to disable auto-scrolling', fakeAsync(() => {
+ const fixture = createComponent(DraggableInScrollableVerticalDropZone);
+ fixture.detectChanges();
+ const item = fixture.componentInstance.dragItems.first.element.nativeElement;
+ const list = fixture.componentInstance.dropInstance.element.nativeElement;
+ const listRect = list.getBoundingClientRect();
+
+ fixture.componentInstance.dropInstance.autoScrollDisabled = true;
+ fixture.detectChanges();
+
+ expect(list.scrollTop).toBe(0);
+
+ startDraggingViaMouse(fixture, item);
+ dispatchMouseEvent(document, 'mousemove',
+ listRect.left + listRect.width / 2, listRect.top + listRect.height);
+ fixture.detectChanges();
+ tickAnimationFrames(20);
+
+ expect(list.scrollTop).toBe(0);
+ }));
+
+ it('should auto-scroll down if the user holds their pointer at bottom edge', fakeAsync(() => {
+ const fixture = createComponent(DraggableInScrollableVerticalDropZone);
+ fixture.detectChanges();
+ const item = fixture.componentInstance.dragItems.first.element.nativeElement;
+ const list = fixture.componentInstance.dropInstance.element.nativeElement;
+ const listRect = list.getBoundingClientRect();
+
+ expect(list.scrollTop).toBe(0);
+
+ startDraggingViaMouse(fixture, item);
+ dispatchMouseEvent(document, 'mousemove',
+ listRect.left + listRect.width / 2, listRect.top + listRect.height);
+ fixture.detectChanges();
+ tickAnimationFrames(20);
+
+ expect(list.scrollTop).toBeGreaterThan(0);
+ }));
+
+ it('should auto-scroll up if the user holds their pointer at top edge', fakeAsync(() => {
+ const fixture = createComponent(DraggableInScrollableVerticalDropZone);
+ fixture.detectChanges();
+ const item = fixture.componentInstance.dragItems.first.element.nativeElement;
+ const list = fixture.componentInstance.dropInstance.element.nativeElement;
+ const listRect = list.getBoundingClientRect();
+ const initialScrollDistance = list.scrollTop = list.scrollHeight;
+
+ startDraggingViaMouse(fixture, item);
+ dispatchMouseEvent(document, 'mousemove', listRect.left + listRect.width / 2, listRect.top);
+ fixture.detectChanges();
+ tickAnimationFrames(20);
+
+ expect(list.scrollTop).toBeLessThan(initialScrollDistance);
+ }));
+
+ it('should auto-scroll right if the user holds their pointer at right edge', fakeAsync(() => {
+ const fixture = createComponent(DraggableInScrollableHorizontalDropZone);
+ fixture.detectChanges();
+ const item = fixture.componentInstance.dragItems.first.element.nativeElement;
+ const list = fixture.componentInstance.dropInstance.element.nativeElement;
+ const listRect = list.getBoundingClientRect();
+
+ expect(list.scrollLeft).toBe(0);
+
+ startDraggingViaMouse(fixture, item);
+ dispatchMouseEvent(document, 'mousemove', listRect.left + listRect.width,
+ listRect.top + listRect.height / 2);
+ fixture.detectChanges();
+ tickAnimationFrames(20);
+
+ expect(list.scrollLeft).toBeGreaterThan(0);
+ }));
+
+ it('should auto-scroll left if the user holds their pointer at left edge', fakeAsync(() => {
+ const fixture = createComponent(DraggableInScrollableHorizontalDropZone);
+ fixture.detectChanges();
+ const item = fixture.componentInstance.dragItems.first.element.nativeElement;
+ const list = fixture.componentInstance.dropInstance.element.nativeElement;
+ const listRect = list.getBoundingClientRect();
+ const initialScrollDistance = list.scrollLeft = list.scrollWidth;
+
+ startDraggingViaMouse(fixture, item);
+ dispatchMouseEvent(document, 'mousemove', listRect.left, listRect.top + listRect.height / 2);
+ fixture.detectChanges();
+ tickAnimationFrames(20);
+
+ expect(list.scrollLeft).toBeLessThan(initialScrollDistance);
+ }));
+
+ it('should stop scrolling if the user moves their pointer away', fakeAsync(() => {
+ const fixture = createComponent(DraggableInScrollableVerticalDropZone);
+ fixture.detectChanges();
+ const item = fixture.componentInstance.dragItems.first.element.nativeElement;
+ const list = fixture.componentInstance.dropInstance.element.nativeElement;
+ const listRect = list.getBoundingClientRect();
+
+ expect(list.scrollTop).toBe(0);
+
+ startDraggingViaMouse(fixture, item);
+ dispatchMouseEvent(document, 'mousemove',
+ listRect.left + listRect.width / 2, listRect.top + listRect.height);
+ fixture.detectChanges();
+ tickAnimationFrames(20);
+
+ const previousScrollTop = list.scrollTop;
+ expect(previousScrollTop).toBeGreaterThan(0);
+
+ // Move the pointer away from the edge of the element.
+ dispatchMouseEvent(document, 'mousemove',
+ listRect.left + listRect.width / 2, listRect.top + listRect.height / 2);
+ fixture.detectChanges();
+ tickAnimationFrames(20);
+
+ expect(list.scrollTop).toBe(previousScrollTop);
+ }));
+
+ it('should stop scrolling if the user stops dragging', fakeAsync(() => {
+ const fixture = createComponent(DraggableInScrollableVerticalDropZone);
+ fixture.detectChanges();
+ const item = fixture.componentInstance.dragItems.first.element.nativeElement;
+ const list = fixture.componentInstance.dropInstance.element.nativeElement;
+ const listRect = list.getBoundingClientRect();
+
+ expect(list.scrollTop).toBe(0);
+
+ startDraggingViaMouse(fixture, item);
+ dispatchMouseEvent(document, 'mousemove',
+ listRect.left + listRect.width / 2, listRect.top + listRect.height);
+ fixture.detectChanges();
+ tickAnimationFrames(20);
+
+ const previousScrollTop = list.scrollTop;
+ expect(previousScrollTop).toBeGreaterThan(0);
+
+ dispatchMouseEvent(document, 'mouseup');
+ fixture.detectChanges();
+ tickAnimationFrames(20);
+
+ expect(list.scrollTop).toBe(previousScrollTop);
+ }));
+
+ it('should auto-scroll viewport down if the pointer is close to bottom edge', fakeAsync(() => {
+ const fixture = createComponent(DraggableInDropZone);
+ fixture.detectChanges();
+
+ const cleanup = makePageScrollable();
+ const item = fixture.componentInstance.dragItems.first.element.nativeElement;
+ const viewportRuler: ViewportRuler = TestBed.get(ViewportRuler);
+ const viewportSize = viewportRuler.getViewportSize();
+
+ expect(viewportRuler.getViewportScrollPosition().top).toBe(0);
+
+ startDraggingViaMouse(fixture, item);
+ dispatchMouseEvent(document, 'mousemove', viewportSize.width / 2, viewportSize.height);
+ fixture.detectChanges();
+ tickAnimationFrames(20);
+
+ expect(viewportRuler.getViewportScrollPosition().top).toBeGreaterThan(0);
+
+ cleanup();
+ }));
+
+ it('should auto-scroll viewport up if the pointer is close to top edge', fakeAsync(() => {
+ const fixture = createComponent(DraggableInDropZone);
+ fixture.detectChanges();
+
+ const cleanup = makePageScrollable();
+ const item = fixture.componentInstance.dragItems.first.element.nativeElement;
+ const viewportRuler: ViewportRuler = TestBed.get(ViewportRuler);
+ const viewportSize = viewportRuler.getViewportSize();
+
+ scrollTo(0, viewportSize.height * 5);
+ const initialScrollDistance = viewportRuler.getViewportScrollPosition().top;
+ expect(initialScrollDistance).toBeGreaterThan(0);
+
+ startDraggingViaMouse(fixture, item);
+ dispatchMouseEvent(document, 'mousemove', viewportSize.width / 2, 0);
+ fixture.detectChanges();
+ tickAnimationFrames(20);
+
+ expect(viewportRuler.getViewportScrollPosition().top).toBeLessThan(initialScrollDistance);
+
+ cleanup();
+ }));
+
+ it('should auto-scroll viewport right if the pointer is near right edge', fakeAsync(() => {
+ const fixture = createComponent(DraggableInDropZone);
+ fixture.detectChanges();
+
+ const cleanup = makePageScrollable('horizontal');
+ const item = fixture.componentInstance.dragItems.first.element.nativeElement;
+ const viewportRuler: ViewportRuler = TestBed.get(ViewportRuler);
+ const viewportSize = viewportRuler.getViewportSize();
+
+ expect(viewportRuler.getViewportScrollPosition().left).toBe(0);
+
+ startDraggingViaMouse(fixture, item);
+ dispatchMouseEvent(document, 'mousemove', viewportSize.width, viewportSize.height / 2);
+ fixture.detectChanges();
+ tickAnimationFrames(20);
+
+ expect(viewportRuler.getViewportScrollPosition().left).toBeGreaterThan(0);
+
+ cleanup();
+ }));
+
+ it('should auto-scroll viewport left if the pointer is close to left edge', fakeAsync(() => {
+ const fixture = createComponent(DraggableInDropZone);
+ fixture.detectChanges();
+
+ const cleanup = makePageScrollable('horizontal');
+ const item = fixture.componentInstance.dragItems.first.element.nativeElement;
+ const viewportRuler: ViewportRuler = TestBed.get(ViewportRuler);
+ const viewportSize = viewportRuler.getViewportSize();
+
+ scrollTo(viewportSize.width * 5, 0);
+ const initialScrollDistance = viewportRuler.getViewportScrollPosition().left;
+ expect(initialScrollDistance).toBeGreaterThan(0);
+
+ startDraggingViaMouse(fixture, item);
+ dispatchMouseEvent(document, 'mousemove', 0, viewportSize.height / 2);
+ fixture.detectChanges();
+ tickAnimationFrames(20);
+
+ expect(viewportRuler.getViewportScrollPosition().left).toBeLessThan(initialScrollDistance);
+
+ cleanup();
+ }));
+
+ it('should auto-scroll the viewport, not the list, when the pointer is over the edge of ' +
+ 'both the list and the viewport', fakeAsync(() => {
+ const fixture = createComponent(DraggableInDropZone);
+ fixture.detectChanges();
+
+ const list = fixture.componentInstance.dropInstance.element.nativeElement;
+ const viewportRuler: ViewportRuler = TestBed.get(ViewportRuler);
+ const item = fixture.componentInstance.dragItems.first.element.nativeElement;
+
+ // Position the list so that its top aligns with the viewport top. That way the pointer
+ // will both over its top edge and the viewport's. We use top instead of bottom, because
+ // bottom behaves weirdly when we run tests on mobile devices.
+ list.style.position = 'fixed';
+ list.style.left = '50%';
+ list.style.top = '0';
+ list.style.margin = '0';
+
+ const listRect = list.getBoundingClientRect();
+ const cleanup = makePageScrollable();
+
+ scrollTo(0, viewportRuler.getViewportSize().height * 5);
+
+ const initialScrollDistance = viewportRuler.getViewportScrollPosition().top;
+ expect(initialScrollDistance).toBeGreaterThan(0);
+ expect(list.scrollTop).toBe(0);
+
+ startDraggingViaMouse(fixture, item);
+ dispatchMouseEvent(document, 'mousemove', listRect.left + listRect.width / 2, 0);
+ fixture.detectChanges();
+ tickAnimationFrames(20);
+
+ expect(viewportRuler.getViewportScrollPosition().top).toBeLessThan(initialScrollDistance);
+ expect(list.scrollTop).toBe(0);
+
+ cleanup();
+ }));
+
});
describe('in a connected drop container', () => {
@@ -3654,6 +4035,7 @@ class StandaloneDraggableWithMultipleHandles {
const DROP_ZONE_FIXTURE_TEMPLATE = `
- `
+ *ngFor="let item of items"
+ [style.width.px]="item.width"
+ [style.margin-right.px]="item.margin"
+ cdkDrag>{{item.value}}
+
+`;
+
+@Component({
+ encapsulation: ViewEncapsulation.None,
+ styles: [HORIZONTAL_FIXTURE_STYLES],
+ template: HORIZONTAL_FIXTURE_TEMPLATE
})
class DraggableInHorizontalDropZone {
@ViewChildren(CdkDrag) dragItems: QueryList;
@@ -3741,6 +4150,31 @@ class DraggableInHorizontalDropZone {
});
}
+
+@Component({
+ template: HORIZONTAL_FIXTURE_TEMPLATE,
+
+ // Note that it needs a margin to ensure that it's not flush against the viewport
+ // edge which will cause the viewport to scroll, rather than the list.
+ styles: [HORIZONTAL_FIXTURE_STYLES, `
+ .drop-list {
+ max-width: 300px;
+ margin: 10vw 0 0 10vw;
+ overflow: auto;
+ white-space: nowrap;
+ }
+ `]
+})
+class DraggableInScrollableHorizontalDropZone extends DraggableInHorizontalDropZone {
+ constructor() {
+ super();
+
+ for (let i = 0; i < 60; i++) {
+ this.items.push({value: `Extra item ${i}`, width: ITEM_WIDTH, margin: 0});
+ }
+ }
+}
+
@Component({
template: `
@@ -4264,10 +4698,10 @@ function getElementSibligsByPosition(element: Element, direction: 'top' | 'left'
* Adds a large element to the page in order to make it scrollable.
* @returns Function that should be used to clean up after the test is done.
*/
-function makePageScrollable() {
+function makePageScrollable(direction: 'vertical' | 'horizontal' = 'vertical') {
const veryTallElement = document.createElement('div');
- veryTallElement.style.width = '100%';
- veryTallElement.style.height = '2000px';
+ veryTallElement.style.width = direction === 'vertical' ? '100%' : '4000px';
+ veryTallElement.style.height = direction === 'vertical' ? '2000px' : '5px';
document.body.appendChild(veryTallElement);
return () => {
@@ -4331,3 +4765,8 @@ function assertUpwardSorting(fixture: ComponentFixture
, items: Element[]) {
fixture.detectChanges();
flush();
}
+
+/** Ticks the specified amount of `requestAnimationFrame`-s. */
+function tickAnimationFrames(amount: number) {
+ tick(16.6 * amount); // Angular turns rAF calls into 16.6ms timeouts in tests.
+}
diff --git a/src/cdk/drag-drop/directives/drop-list.ts b/src/cdk/drag-drop/directives/drop-list.ts
index 8a34037e0adc..c62a38cb8a5c 100644
--- a/src/cdk/drag-drop/directives/drop-list.ts
+++ b/src/cdk/drag-drop/directives/drop-list.ts
@@ -129,6 +129,10 @@ export class CdkDropList implements CdkDropListContainer, AfterContentI
@Input('cdkDropListEnterPredicate')
enterPredicate: (drag: CdkDrag, drop: CdkDropList) => boolean = () => true
+ /** Whether to auto-scroll the view when the user moves their pointer close to the edges. */
+ @Input('cdkDropListAutoScrollDisabled')
+ autoScrollDisabled: boolean = false;
+
/** Emits when the user drops an item inside the container. */
@Output('cdkDropListDropped')
dropped: EventEmitter> = new EventEmitter>();
@@ -298,6 +302,7 @@ export class CdkDropList implements CdkDropListContainer, AfterContentI
ref.disabled = this.disabled;
ref.lockAxis = this.lockAxis;
ref.sortingDisabled = this.sortingDisabled;
+ ref.autoScrollDisabled = this.autoScrollDisabled;
ref
.connectedTo(siblings.filter(drop => drop && drop !== this).map(list => list._dropListRef))
.withOrientation(this.orientation);
diff --git a/src/cdk/drag-drop/drag-drop-registry.spec.ts b/src/cdk/drag-drop/drag-drop-registry.spec.ts
index b7a62a2319ab..8af6d8407ae3 100644
--- a/src/cdk/drag-drop/drag-drop-registry.spec.ts
+++ b/src/cdk/drag-drop/drag-drop-registry.spec.ts
@@ -155,7 +155,7 @@ describe('DragDropRegistry', () => {
pointerMoveSubscription.unsubscribe();
});
- it('should not emit pointer events when dragging is over (mutli touch)', () => {
+ it('should not emit pointer events when dragging is over (multi touch)', () => {
const firstItem = testComponent.dragItems.first;
// First finger down
@@ -211,15 +211,6 @@ describe('DragDropRegistry', () => {
expect(event.defaultPrevented).toBe(true);
});
- it('should not prevent the default `wheel` actions when nothing is being dragged', () => {
- expect(dispatchFakeEvent(document, 'wheel').defaultPrevented).toBe(false);
- });
-
- it('should prevent the default `wheel` action when an item is being dragged', () => {
- registry.startDragging(testComponent.dragItems.first, createMouseEvent('mousedown'));
- expect(dispatchFakeEvent(document, 'wheel').defaultPrevented).toBe(true);
- });
-
it('should not prevent the default `selectstart` actions when nothing is being dragged', () => {
expect(dispatchFakeEvent(document, 'selectstart').defaultPrevented).toBe(false);
});
@@ -229,6 +220,26 @@ describe('DragDropRegistry', () => {
expect(dispatchFakeEvent(document, 'selectstart').defaultPrevented).toBe(true);
});
+ it('should dispatch `scroll` events if the viewport is scrolled while dragging', () => {
+ const spy = jasmine.createSpy('scroll spy');
+ const subscription = registry.scroll.subscribe(spy);
+
+ registry.startDragging(testComponent.dragItems.first, createMouseEvent('mousedown'));
+ dispatchFakeEvent(document, 'scroll');
+
+ expect(spy).toHaveBeenCalled();
+ subscription.unsubscribe();
+ });
+
+ it('should not dispatch `scroll` events when not dragging', () => {
+ const spy = jasmine.createSpy('scroll spy');
+ const subscription = registry.scroll.subscribe(spy);
+
+ dispatchFakeEvent(document, 'scroll');
+
+ expect(spy).not.toHaveBeenCalled();
+ subscription.unsubscribe();
+ });
});
diff --git a/src/cdk/drag-drop/drag-drop-registry.ts b/src/cdk/drag-drop/drag-drop-registry.ts
index 1151a9c60edd..7a46f667b696 100644
--- a/src/cdk/drag-drop/drag-drop-registry.ts
+++ b/src/cdk/drag-drop/drag-drop-registry.ts
@@ -56,6 +56,9 @@ export class DragDropRegistry implements OnDestroy {
*/
readonly pointerUp: Subject = new Subject();
+ /** Emits when the viewport has been scrolled while the user is dragging an item. */
+ readonly scroll: Subject = new Subject();
+
constructor(
private _ngZone: NgZone,
@Inject(DOCUMENT) _document: any) {
@@ -136,6 +139,9 @@ export class DragDropRegistry implements OnDestroy {
handler: (e: Event) => this.pointerUp.next(e as TouchEvent | MouseEvent),
options: true
})
+ .set('scroll', {
+ handler: (e: Event) => this.scroll.next(e)
+ })
// Preventing the default action on `mousemove` isn't enough to disable text selection
// on Safari so we need to prevent the selection event as well. Alternatively this can
// be done by setting `user-select: none` on the `body`, however it has causes a style
@@ -145,15 +151,6 @@ export class DragDropRegistry implements OnDestroy {
options: activeCapturingEventOptions
});
- // TODO(crisbeto): prevent mouse wheel scrolling while
- // dragging until we've set up proper scroll handling.
- if (!isTouchEvent) {
- this._globalListeners.set('wheel', {
- handler: this._preventDefaultWhileDragging,
- options: activeCapturingEventOptions
- });
- }
-
this._ngZone.runOutsideAngular(() => {
this._globalListeners.forEach((config, name) => {
this._document.addEventListener(name, config.handler, config.options);
diff --git a/src/cdk/drag-drop/drag-drop.ts b/src/cdk/drag-drop/drag-drop.ts
index b86466ee2c9f..a62b3a356ba0 100644
--- a/src/cdk/drag-drop/drag-drop.ts
+++ b/src/cdk/drag-drop/drag-drop.ts
@@ -47,6 +47,7 @@ export class DragDrop {
* @param element Element to which to attach the drop list functionality.
*/
createDropList(element: ElementRef | HTMLElement): DropListRef {
- return new DropListRef(element, this._dragDropRegistry, this._document);
+ return new DropListRef(element, this._dragDropRegistry, this._document, this._ngZone,
+ this._viewportRuler);
}
}
diff --git a/src/cdk/drag-drop/drag-ref.ts b/src/cdk/drag-drop/drag-ref.ts
index 34dc1c1c4fb3..4fe7a1890754 100644
--- a/src/cdk/drag-drop/drag-ref.ts
+++ b/src/cdk/drag-drop/drag-ref.ts
@@ -12,6 +12,7 @@ import {Direction} from '@angular/cdk/bidi';
import {normalizePassiveListenerOptions} from '@angular/cdk/platform';
import {coerceBooleanProperty, coerceElement} from '@angular/cdk/coercion';
import {Subscription, Subject, Observable} from 'rxjs';
+import {startWith} from 'rxjs/operators';
import {DropListRefInternal as DropListRef} from './drop-list-ref';
import {DragDropRegistry} from './drag-drop-registry';
import {extendStyles, toggleNativeDragInteractions} from './drag-styling';
@@ -46,7 +47,6 @@ const activeEventListenerOptions = normalizePassiveListenerOptions({passive: fal
*/
const MOUSE_EVENT_IGNORE_TIME = 800;
-// TODO(crisbeto): add auto-scrolling functionality.
// TODO(crisbeto): add an API for moving a draggable up/down the
// list programmatically. Useful for keyboard controls.
@@ -155,6 +155,9 @@ export class DragRef {
/** Subscription to the event that is dispatched when the user lifts their pointer. */
private _pointerUpSubscription = Subscription.EMPTY;
+ /** Subscription to the viewport being scrolled. */
+ private _scrollSubscription = Subscription.EMPTY;
+
/**
* Time at which the last touch event occurred. Used to avoid firing the same
* events multiple times on touch devices where the browser will fire a fake
@@ -446,10 +449,20 @@ export class DragRef {
return this;
}
+ /** Updates the item's sort order based on the last-known pointer position. */
+ _sortFromLastPointerPosition() {
+ const position = this._pointerPositionAtLastDirectionChange;
+
+ if (position && this._dropContainer) {
+ this._updateActiveDropContainer(position);
+ }
+ }
+
/** Unsubscribes from the global subscriptions. */
private _removeSubscriptions() {
this._pointerMoveSubscription.unsubscribe();
this._pointerUpSubscription.unsubscribe();
+ this._scrollSubscription.unsubscribe();
}
/** Destroys the preview element and its ViewRef. */
@@ -593,7 +606,14 @@ export class DragRef {
this.released.next({source: this});
- if (!this._dropContainer) {
+ if (this._dropContainer) {
+ // Stop scrolling immediately, instead of waiting for the animation to finish.
+ this._dropContainer._stopScrolling();
+ this._animatePreviewToPlaceholder().then(() => {
+ this._cleanupDragArtifacts(event);
+ this._dragDropRegistry.stopDragging(this);
+ });
+ } else {
// Convert the active transform into a passive one. This means that next time
// the user starts dragging the item, its position will be calculated relatively
// to the new passive transform.
@@ -606,13 +626,7 @@ export class DragRef {
});
});
this._dragDropRegistry.stopDragging(this);
- return;
}
-
- this._animatePreviewToPlaceholder().then(() => {
- this._cleanupDragArtifacts(event);
- this._dragDropRegistry.stopDragging(this);
- });
}
/** Starts the dragging sequence. */
@@ -695,8 +709,9 @@ export class DragRef {
this._removeSubscriptions();
this._pointerMoveSubscription = this._dragDropRegistry.pointerMove.subscribe(this._pointerMove);
this._pointerUpSubscription = this._dragDropRegistry.pointerUp.subscribe(this._pointerUp);
-
- this._scrollPosition = this._viewportRuler.getViewportScrollPosition();
+ this._scrollSubscription = this._dragDropRegistry.scroll.pipe(startWith(null)).subscribe(() => {
+ this._scrollPosition = this._viewportRuler.getViewportScrollPosition();
+ });
if (this._boundaryElement) {
this._boundaryRect = this._boundaryElement.getBoundingClientRect();
@@ -789,6 +804,7 @@ export class DragRef {
});
}
+ this._dropContainer!._startScrollingIfNecessary(x, y);
this._dropContainer!._sortItem(this, x, y, this._pointerDirectionDelta);
this._preview.style.transform =
getTransform(x - this._pickupPositionInElement.x, y - this._pickupPositionInElement.y);
diff --git a/src/cdk/drag-drop/drop-list-ref.ts b/src/cdk/drag-drop/drop-list-ref.ts
index 68c9d5e56d65..21b9cf2760ff 100644
--- a/src/cdk/drag-drop/drop-list-ref.ts
+++ b/src/cdk/drag-drop/drop-list-ref.ts
@@ -6,15 +6,16 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {ElementRef} from '@angular/core';
-import {DragDropRegistry} from './drag-drop-registry';
+import {ElementRef, NgZone} from '@angular/core';
import {Direction} from '@angular/cdk/bidi';
import {coerceElement} from '@angular/cdk/coercion';
-import {Subject} from 'rxjs';
+import {ViewportRuler} from '@angular/cdk/scrolling';
+import {Subject, Subscription, interval, animationFrameScheduler} from 'rxjs';
+import {takeUntil} from 'rxjs/operators';
import {moveItemInArray} from './drag-utils';
+import {DragDropRegistry} from './drag-drop-registry';
import {DragRefInternal as DragRef, Point} from './drag-ref';
-
/** Counter used to generate unique ids for drop refs. */
let _uniqueIdCounter = 0;
@@ -24,6 +25,18 @@ let _uniqueIdCounter = 0;
*/
const DROP_PROXIMITY_THRESHOLD = 0.05;
+/**
+ * Proximity, as a ratio to width/height at which to start auto-scrolling the drop list or the
+ * viewport. The value comes from trying it out manually until it feels right.
+ */
+const SCROLL_PROXIMITY_THRESHOLD = 0.05;
+
+/**
+ * Number of pixels to scroll for each frame when auto-scrolling an element.
+ * The value comes from trying it out manually until it feels right.
+ */
+const AUTO_SCROLL_STEP = 2;
+
/**
* Entry in the position cache for draggable items.
* @docs-private
@@ -37,6 +50,18 @@ interface CachedItemPosition {
offset: number;
}
+/** Object holding the scroll position of something. */
+interface ScrollPosition {
+ top: number;
+ left: number;
+}
+
+/** Vertical direction in which we can auto-scroll. */
+const enum AutoScrollVerticalDirection {NONE, UP, DOWN}
+
+/** Horizontal direction in which we can auto-scroll. */
+const enum AutoScrollHorizontalDirection {NONE, LEFT, RIGHT}
+
/**
* Internal compile-time-only representation of a `DropListRef`.
* Used to avoid circular import issues between the `DropListRef` and the `DragRef`.
@@ -70,6 +95,12 @@ export class DropListRef {
/** Locks the position of the draggable elements inside the container along the specified axis. */
lockAxis: 'x' | 'y';
+ /**
+ * Whether auto-scrolling the view when the user
+ * moves their pointer close to the edges is disabled.
+ */
+ autoScrollDisabled: boolean = false;
+
/**
* Function that is used to determine whether an item
* is allowed to be moved into a drop container.
@@ -118,6 +149,12 @@ export class DropListRef {
/** Cache of the dimensions of all the items inside the container. */
private _itemPositions: CachedItemPosition[] = [];
+ /** Keeps track of the container's scroll position. */
+ private _scrollPosition: ScrollPosition = {top: 0, left: 0};
+
+ /** Keeps track of the scroll position of the viewport. */
+ private _viewportScrollPosition: ScrollPosition = {top: 0, left: 0};
+
/** Cached `ClientRect` of the drop list. */
private _clientRect: ClientRect;
@@ -149,10 +186,31 @@ export class DropListRef {
/** Layout direction of the drop list. */
private _direction: Direction = 'ltr';
+ /** Subscription to the window being scrolled. */
+ private _viewportScrollSubscription = Subscription.EMPTY;
+
+ /** Vertical direction in which the list is currently scrolling. */
+ private _verticalScrollDirection = AutoScrollVerticalDirection.NONE;
+
+ /** Horizontal direction in which the list is currently scrolling. */
+ private _horizontalScrollDirection = AutoScrollHorizontalDirection.NONE;
+
+ /** Node that is being auto-scrolled. */
+ private _scrollNode: HTMLElement | Window;
+
+ /** Used to signal to the current auto-scroll sequence when to stop. */
+ private _stopScrollTimers = new Subject();
+
constructor(
element: ElementRef | HTMLElement,
private _dragDropRegistry: DragDropRegistry,
- _document: any) {
+ _document: any,
+ /**
+ * @deprecated _ngZone and _viewportRuler parameters to be made required.
+ * @breaking-change 9.0.0
+ */
+ private _ngZone?: NgZone,
+ private _viewportRuler?: ViewportRuler) {
_dragDropRegistry.registerDropContainer(this);
this._document = _document;
this.element = element instanceof ElementRef ? element.nativeElement : element;
@@ -160,12 +218,16 @@ export class DropListRef {
/** Removes the drop list functionality from the DOM element. */
dispose() {
+ this._stopScrolling();
+ this._stopScrollTimers.complete();
+ this._removeListeners();
this.beforeStarted.complete();
this.entered.complete();
this.exited.complete();
this.dropped.complete();
this.sorted.complete();
this._activeSiblings.clear();
+ this._scrollNode = null!;
this._dragDropRegistry.removeDropContainer(this);
}
@@ -176,10 +238,31 @@ export class DropListRef {
/** Starts dragging an item. */
start(): void {
+ const element = coerceElement(this.element);
this.beforeStarted.next();
this._isDragging = true;
this._cacheItems();
this._siblings.forEach(sibling => sibling._startReceiving(this));
+ this._removeListeners();
+
+ // @breaking-change 9.0.0 Remove check for _ngZone once it's marked as a required param.
+ if (this._ngZone) {
+ this._ngZone.runOutsideAngular(() => element.addEventListener('scroll', this._handleScroll));
+ } else {
+ element.addEventListener('scroll', this._handleScroll);
+ }
+
+ // @breaking-change 9.0.0 Remove check for _viewportRuler once it's marked as a required param.
+ if (this._viewportRuler) {
+ this._viewportScrollPosition = this._viewportRuler.getViewportScrollPosition();
+ this._viewportScrollSubscription = this._dragDropRegistry.scroll.subscribe(() => {
+ if (this.isDragging()) {
+ const newPosition = this._viewportRuler!.getViewportScrollPosition();
+ this._updateAfterScroll(this._viewportScrollPosition, newPosition.top, newPosition.left,
+ this._clientRect);
+ }
+ });
+ }
}
/**
@@ -419,9 +502,76 @@ export class DropListRef {
});
}
+ /**
+ * Checks whether the user's pointer is close to the edges of either the
+ * viewport or the drop list and starts the auto-scroll sequence.
+ * @param pointerX User's pointer position along the x axis.
+ * @param pointerY User's pointer position along the y axis.
+ */
+ _startScrollingIfNecessary(pointerX: number, pointerY: number) {
+ if (this.autoScrollDisabled) {
+ return;
+ }
+
+ let scrollNode: HTMLElement | Window | undefined;
+ let verticalScrollDirection = AutoScrollVerticalDirection.NONE;
+ let horizontalScrollDirection = AutoScrollHorizontalDirection.NONE;
+
+ // @breaking-change 9.0.0 Remove null check for _viewportRuler once it's a required parameter.
+ // Check whether we're in range to scroll the viewport.
+ if (this._viewportRuler) {
+ const {width, height} = this._viewportRuler.getViewportSize();
+ const clientRect = {width, height, top: 0, right: width, bottom: height, left: 0};
+ verticalScrollDirection = getVerticalScrollDirection(clientRect, pointerY);
+ horizontalScrollDirection = getHorizontalScrollDirection(clientRect, pointerX);
+ scrollNode = window;
+ }
+
+ // If we couldn't find a scroll direction based on the
+ // window, try with the container, if the pointer is close by.
+ if (!verticalScrollDirection && !horizontalScrollDirection &&
+ this._isPointerNearDropContainer(pointerX, pointerY)) {
+ verticalScrollDirection = getVerticalScrollDirection(this._clientRect, pointerY);
+ horizontalScrollDirection = getHorizontalScrollDirection(this._clientRect, pointerX);
+ scrollNode = coerceElement(this.element);
+ }
+
+ // TODO(crisbeto): we also need to account for whether the view or element are scrollable in
+ // the first place. With the current approach we'll still try to scroll them, but it just
+ // won't do anything. The only case where this is relevant is that if we have a scrollable
+ // list close to the viewport edge where the viewport isn't scrollable. In this case the
+ // we'll be trying to scroll the viewport rather than the list.
+
+ if (scrollNode && (verticalScrollDirection !== this._verticalScrollDirection ||
+ horizontalScrollDirection !== this._horizontalScrollDirection ||
+ scrollNode !== this._scrollNode)) {
+ this._verticalScrollDirection = verticalScrollDirection;
+ this._horizontalScrollDirection = horizontalScrollDirection;
+ this._scrollNode = scrollNode;
+
+ if ((verticalScrollDirection || horizontalScrollDirection) && scrollNode) {
+ // @breaking-change 9.0.0 Remove null check for `_ngZone` once it is made required.
+ if (this._ngZone) {
+ this._ngZone.runOutsideAngular(this._startScrollInterval);
+ } else {
+ this._startScrollInterval();
+ }
+ } else {
+ this._stopScrolling();
+ }
+ }
+ }
+
+ /** Stops any currently-running auto-scroll sequences. */
+ _stopScrolling() {
+ this._stopScrollTimers.next();
+ }
+
/** Caches the position of the drop list. */
private _cacheOwnPosition() {
- this._clientRect = coerceElement(this.element).getBoundingClientRect();
+ const element = coerceElement(this.element);
+ this._clientRect = getMutableClientRect(element);
+ this._scrollPosition = {top: element.scrollTop, left: element.scrollLeft};
}
/** Refreshes the position cache of the items and sibling containers. */
@@ -434,24 +584,7 @@ export class DropListRef {
// placeholder, because the element is hidden.
drag.getPlaceholderElement() :
drag.getRootElement();
- const clientRect = elementToMeasure.getBoundingClientRect();
-
- return {
- drag,
- offset: 0,
- // We need to clone the `clientRect` here, because all the values on it are readonly
- // and we need to be able to update them. Also we can't use a spread here, because
- // the values on a `ClientRect` aren't own properties. See:
- // https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect#Notes
- clientRect: {
- top: clientRect.top,
- right: clientRect.right,
- bottom: clientRect.bottom,
- left: clientRect.left,
- width: clientRect.width,
- height: clientRect.height
- }
- };
+ return {drag, offset: 0, clientRect: getMutableClientRect(elementToMeasure)};
}).sort((a, b) => {
return isHorizontal ? a.clientRect.left - b.clientRect.left :
a.clientRect.top - b.clientRect.top;
@@ -469,6 +602,8 @@ export class DropListRef {
this._itemPositions = [];
this._previousSwap.drag = null;
this._previousSwap.delta = 0;
+ this._stopScrolling();
+ this._removeListeners();
}
/**
@@ -581,6 +716,84 @@ export class DropListRef {
this._cacheOwnPosition();
}
+ /**
+ * Updates the internal state of the container after a scroll event has happened.
+ * @param scrollPosition Object that is keeping track of the scroll position.
+ * @param newTop New top scroll position.
+ * @param newLeft New left scroll position.
+ * @param extraClientRect Extra `ClientRect` object that should be updated, in addition to the
+ * ones of the drag items. Useful when the viewport has been scrolled and we also need to update
+ * the `ClientRect` of the list.
+ */
+ private _updateAfterScroll(scrollPosition: ScrollPosition, newTop: number, newLeft: number,
+ extraClientRect?: ClientRect) {
+ const topDifference = scrollPosition.top - newTop;
+ const leftDifference = scrollPosition.left - newLeft;
+
+ if (extraClientRect) {
+ adjustClientRect(extraClientRect, topDifference, leftDifference);
+ }
+
+ // Since we know the amount that the user has scrolled we can shift all of the client rectangles
+ // ourselves. This is cheaper than re-measuring everything and we can avoid inconsistent
+ // behavior where we might be measuring the element before its position has changed.
+ this._itemPositions.forEach(({clientRect}) => {
+ adjustClientRect(clientRect, topDifference, leftDifference);
+ });
+
+ // We need two loops for this, because we want all of the cached
+ // positions to be up-to-date before we re-sort the item.
+ this._itemPositions.forEach(({drag}) => {
+ if (this._dragDropRegistry.isDragging(drag)) {
+ // We need to re-sort the item manually, because the pointer move
+ // events won't be dispatched while the user is scrolling.
+ drag._sortFromLastPointerPosition();
+ }
+ });
+
+ scrollPosition.top = newTop;
+ scrollPosition.left = newLeft;
+ }
+
+ /** Handles the container being scrolled. Has to be an arrow function to preserve the context. */
+ private _handleScroll = () => {
+ if (!this.isDragging()) {
+ return;
+ }
+
+ const element = coerceElement(this.element);
+ this._updateAfterScroll(this._scrollPosition, element.scrollTop, element.scrollLeft);
+ }
+
+ /** Removes the event listeners associated with this drop list. */
+ private _removeListeners() {
+ coerceElement(this.element).removeEventListener('scroll', this._handleScroll);
+ this._viewportScrollSubscription.unsubscribe();
+ }
+
+ /** Starts the interval that'll auto-scroll the element. */
+ private _startScrollInterval = () => {
+ this._stopScrolling();
+
+ interval(0, animationFrameScheduler)
+ .pipe(takeUntil(this._stopScrollTimers))
+ .subscribe(() => {
+ const node = this._scrollNode;
+
+ if (this._verticalScrollDirection === AutoScrollVerticalDirection.UP) {
+ incrementVerticalScroll(node, -AUTO_SCROLL_STEP);
+ } else if (this._verticalScrollDirection === AutoScrollVerticalDirection.DOWN) {
+ incrementVerticalScroll(node, AUTO_SCROLL_STEP);
+ }
+
+ if (this._horizontalScrollDirection === AutoScrollHorizontalDirection.LEFT) {
+ incrementHorizontalScroll(node, -AUTO_SCROLL_STEP);
+ } else if (this._horizontalScrollDirection === AutoScrollHorizontalDirection.RIGHT) {
+ incrementHorizontalScroll(node, AUTO_SCROLL_STEP);
+ }
+ });
+ }
+
/**
* Checks whether the user's pointer is positioned over the container.
* @param x Pointer position along the X axis.
@@ -671,7 +884,7 @@ function adjustClientRect(clientRect: ClientRect, top: number, left: number) {
/**
* Finds the index of an item that matches a predicate function. Used as an equivalent
- * of `Array.prototype.find` which isn't part of the standard Google typings.
+ * of `Array.prototype.findIndex` which isn't part of the standard Google typings.
* @param array Array in which to look for matches.
* @param predicate Function used to determine whether an item is a match.
*/
@@ -698,3 +911,86 @@ function isInsideClientRect(clientRect: ClientRect, x: number, y: number) {
const {top, bottom, left, right} = clientRect;
return y >= top && y <= bottom && x >= left && x <= right;
}
+
+
+/** Gets a mutable version of an element's bounding `ClientRect`. */
+function getMutableClientRect(element: Element): ClientRect {
+ const clientRect = element.getBoundingClientRect();
+
+ // We need to clone the `clientRect` here, because all the values on it are readonly
+ // and we need to be able to update them. Also we can't use a spread here, because
+ // the values on a `ClientRect` aren't own properties. See:
+ // https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect#Notes
+ return {
+ top: clientRect.top,
+ right: clientRect.right,
+ bottom: clientRect.bottom,
+ left: clientRect.left,
+ width: clientRect.width,
+ height: clientRect.height
+ };
+}
+
+/**
+ * Increments the vertical scroll position of a node.
+ * @param node Node whose scroll position should change.
+ * @param amount Amount of pixels that the `node` should be scrolled.
+ */
+function incrementVerticalScroll(node: HTMLElement | Window, amount: number) {
+ if (node === window) {
+ (node as Window).scrollBy(0, amount);
+ } else {
+ // Ideally we could use `Element.scrollBy` here as well, but IE and Edge don't support it.
+ (node as HTMLElement).scrollTop += amount;
+ }
+}
+
+/**
+ * Increments the horizontal scroll position of a node.
+ * @param node Node whose scroll position should change.
+ * @param amount Amount of pixels that the `node` should be scrolled.
+ */
+function incrementHorizontalScroll(node: HTMLElement | Window, amount: number) {
+ if (node === window) {
+ (node as Window).scrollBy(amount, 0);
+ } else {
+ // Ideally we could use `Element.scrollBy` here as well, but IE and Edge don't support it.
+ (node as HTMLElement).scrollLeft += amount;
+ }
+}
+
+/**
+ * Gets whether the vertical auto-scroll direction of a node.
+ * @param clientRect Dimensions of the node.
+ * @param pointerY Position of the user's pointer along the y axis.
+ */
+function getVerticalScrollDirection(clientRect: ClientRect, pointerY: number) {
+ const {top, bottom, height} = clientRect;
+ const yThreshold = height * SCROLL_PROXIMITY_THRESHOLD;
+
+ if (pointerY >= top - yThreshold && pointerY <= top + yThreshold) {
+ return AutoScrollVerticalDirection.UP;
+ } else if (pointerY >= bottom - yThreshold && pointerY <= bottom + yThreshold) {
+ return AutoScrollVerticalDirection.DOWN;
+ }
+
+ return AutoScrollVerticalDirection.NONE;
+}
+
+/**
+ * Gets whether the horizontal auto-scroll direction of a node.
+ * @param clientRect Dimensions of the node.
+ * @param pointerX Position of the user's pointer along the x axis.
+ */
+function getHorizontalScrollDirection(clientRect: ClientRect, pointerX: number) {
+ const {left, right, width} = clientRect;
+ const xThreshold = width * SCROLL_PROXIMITY_THRESHOLD;
+
+ if (pointerX >= left - xThreshold && pointerX <= left + xThreshold) {
+ return AutoScrollHorizontalDirection.LEFT;
+ } else if (pointerX >= right - xThreshold && pointerX <= right + xThreshold) {
+ return AutoScrollHorizontalDirection.RIGHT;
+ }
+
+ return AutoScrollHorizontalDirection.NONE;
+}
diff --git a/tools/public_api_guard/cdk/drag-drop.d.ts b/tools/public_api_guard/cdk/drag-drop.d.ts
index 7706874fc96c..0a421d9e4611 100644
--- a/tools/public_api_guard/cdk/drag-drop.d.ts
+++ b/tools/public_api_guard/cdk/drag-drop.d.ts
@@ -138,6 +138,7 @@ export interface CdkDragStart {
export declare class CdkDropList implements CdkDropListContainer, AfterContentInit, OnDestroy {
_draggables: QueryList;
_dropListRef: DropListRef>;
+ autoScrollDisabled: boolean;
connectedTo: (CdkDropList | string)[] | CdkDropList | string;
data: T;
disabled: boolean;
@@ -211,6 +212,7 @@ export declare class DragDropRegistry implements OnDestroy {
readonly pointerMove: Subject;
readonly pointerUp: Subject;
+ readonly scroll: Subject;
constructor(_ngZone: NgZone, _document: any);
getDropContainer(id: string): C | undefined;
isDragging(drag: I): boolean;
@@ -272,6 +274,7 @@ export declare class DragRef {
source: DragRef;
}>;
constructor(element: ElementRef | HTMLElement, _config: DragRefConfig, _document: Document, _ngZone: NgZone, _viewportRuler: ViewportRuler, _dragDropRegistry: DragDropRegistry);
+ _sortFromLastPointerPosition(): void;
_withDropContainer(container: DropListRef): void;
disableHandle(handle: HTMLElement): void;
dispose(): void;
@@ -296,6 +299,7 @@ export interface DragRefConfig {
}
export declare class DropListRef {
+ autoScrollDisabled: boolean;
beforeStarted: Subject;
data: T;
disabled: boolean;
@@ -328,7 +332,8 @@ export declare class DropListRef {
item: DragRef;
}>;
sortingDisabled: boolean;
- constructor(element: ElementRef | HTMLElement, _dragDropRegistry: DragDropRegistry, _document: any);
+ constructor(element: ElementRef | HTMLElement, _dragDropRegistry: DragDropRegistry, _document: any,
+ _ngZone?: NgZone | undefined, _viewportRuler?: ViewportRuler | undefined);
_canReceive(item: DragRef, x: number, y: number): boolean;
_getSiblingContainerFromPosition(item: DragRef, x: number, y: number): DropListRef | undefined;
_isOverContainer(x: number, y: number): boolean;
@@ -337,7 +342,9 @@ export declare class DropListRef {
y: number;
}): void;
_startReceiving(sibling: DropListRef): void;
+ _startScrollingIfNecessary(pointerX: number, pointerY: number): void;
_stopReceiving(sibling: DropListRef): void;
+ _stopScrolling(): void;
connectedTo(connectedTo: DropListRef[]): this;
dispose(): void;
drop(item: DragRef, currentIndex: number, previousContainer: DropListRef, isPointerOverContainer: boolean, distance?: Point): void;