Skip to content
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

feat(drag-drop): support scrolling parent elements apart from list and viewport #18082

Merged
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
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 @@ -3375,6 +3375,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 @@ -4659,6 +4677,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