Skip to content

Commit

Permalink
feat(drag-drop): support scrolling parent elements apart from list an…
Browse files Browse the repository at this point in the history
…d viewport (#18082)

Currently for performance reasons we only support scrolling within the drop list itself or the viewport, however in some cases the scrollable container might be different. These changes add a new input that consumers can use to tell the CDK which other parents can be scrolled.

Fixes #18072.
Relates to #13588.
  • Loading branch information
crisbeto authored and jelbourn committed Jan 22, 2020
1 parent 3cbc94a commit c319431
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 75 deletions.
46 changes: 44 additions & 2 deletions src/cdk/drag-drop/directives/drag.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +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 {ViewportRuler, ScrollingModule} from '@angular/cdk/scrolling';
import {_supportsShadowDom} from '@angular/cdk/platform';
import {of as observableOf} from 'rxjs';

Expand All @@ -47,7 +47,7 @@ describe('CdkDrag', () => {
extraDeclarations: Type<any>[] = []): ComponentFixture<T> {
TestBed
.configureTestingModule({
imports: [DragDropModule],
imports: [DragDropModule, ScrollingModule],
declarations: [componentType, PassthroughComponent, ...extraDeclarations],
providers: [
{
Expand Down Expand Up @@ -3411,6 +3411,24 @@ describe('CdkDrag', () => {
cleanup();
}));

it('should be able to auto-scroll a parent container', fakeAsync(() => {
const fixture = createComponent(DraggableInScrollableParentContainer);
fixture.detectChanges();
const item = fixture.componentInstance.dragItems.first.element.nativeElement;
const container = fixture.nativeElement.querySelector('.container');
const containerRect = container.getBoundingClientRect();

expect(container.scrollTop).toBe(0);

startDraggingViaMouse(fixture, item);
dispatchMouseEvent(document, 'mousemove',
containerRect.left + containerRect.width / 2, containerRect.top + containerRect.height);
fixture.detectChanges();
tickAnimationFrames(20);

expect(container.scrollTop).toBeGreaterThan(0);
}));

it('should pick up descendants inside of containers', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZoneWithContainer);
fixture.detectChanges();
Expand Down Expand Up @@ -4695,6 +4713,30 @@ class DraggableInScrollableVerticalDropZone extends DraggableInDropZone {
}
}

@Component({
template: '<div class="container" cdkScrollable>' + DROP_ZONE_FIXTURE_TEMPLATE + '</div>',

// 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: [`
.container {
max-height: 200px;
overflow: auto;
margin: 10vw 0 0 10vw;
}
`]
})
class DraggableInScrollableParentContainer extends DraggableInDropZone {
constructor() {
super();

for (let i = 0; i < 60; i++) {
this.items.push({value: `Extra item ${i}`, height: ITEM_HEIGHT, margin: 0});
}
}
}


@Component({
// Note that we need the blank `ngSwitch` below to hit the code path that we're testing.
template: `
Expand Down
17 changes: 16 additions & 1 deletion src/cdk/drag-drop/directives/drop-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
AfterContentInit,
} from '@angular/core';
import {Directionality} from '@angular/cdk/bidi';
import {ScrollDispatcher} from '@angular/cdk/scrolling';
import {CdkDrag, CDK_DROP_LIST} from './drag';
import {CdkDragDrop, CdkDragEnter, CdkDragExit, CdkDragSortEvent} from '../drag-events';
import {CdkDropListGroup} from './drop-list-group';
Expand Down Expand Up @@ -148,7 +149,13 @@ export class CdkDropList<T = any> implements AfterContentInit, OnDestroy {
/** Element that the drop list is attached to. */
public element: ElementRef<HTMLElement>, dragDrop: DragDrop,
private _changeDetectorRef: ChangeDetectorRef, @Optional() private _dir?: Directionality,
@Optional() @SkipSelf() private _group?: CdkDropListGroup<CdkDropList>) {
@Optional() @SkipSelf() private _group?: CdkDropListGroup<CdkDropList>,

/**
* @deprecated _scrollDispatcher parameter to become required.
* @breaking-change 11.0.0
*/
private _scrollDispatcher?: ScrollDispatcher) {
this._dropListRef = dragDrop.createDropList(element);
this._dropListRef.data = this;
this._dropListRef.enterPredicate = (drag: DragRef<CdkDrag>, drop: DropListRef<CdkDropList>) => {
Expand All @@ -165,6 +172,14 @@ export class CdkDropList<T = any> implements AfterContentInit, OnDestroy {
}

ngAfterContentInit() {
// @breaking-change 11.0.0 Remove null check for _scrollDispatcher once it's required.
if (this._scrollDispatcher) {
const scrollableParents = this._scrollDispatcher
.getAncestorScrollContainers(this.element)
.map(scrollable => scrollable.getElementRef().nativeElement);
this._dropListRef.withScrollableParents(scrollableParents);
}

this._draggables.changes
.pipe(startWith(this._draggables), takeUntil(this._destroyed))
.subscribe((items: QueryList<CdkDrag>) => {
Expand Down
Loading

0 comments on commit c319431

Please sign in to comment.