diff --git a/src/cdk/drag-drop/directives/drag.spec.ts b/src/cdk/drag-drop/directives/drag.spec.ts index 81e161776eed..f72990a26f2e 100644 --- a/src/cdk/drag-drop/directives/drag.spec.ts +++ b/src/cdk/drag-drop/directives/drag.spec.ts @@ -96,7 +96,7 @@ describe('CdkDrag', () => { const fixture = createComponent(StandaloneDraggable); fixture.detectChanges(); - const cleanup = makePageScrollable(); + const cleanup = makeScrollable(); const dragElement = fixture.componentInstance.dragElement.nativeElement; scrollTo(0, 500); @@ -126,7 +126,7 @@ describe('CdkDrag', () => { fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; - const cleanup = makePageScrollable(); + const cleanup = makeScrollable(); scrollTo(0, 500); expect(dragElement.style.transform).toBeFalsy(); @@ -256,7 +256,7 @@ describe('CdkDrag', () => { fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; - const cleanup = makePageScrollable(); + const cleanup = makeScrollable(); scrollTo(0, 500); expect(dragElement.style.transform).toBeFalsy(); @@ -285,7 +285,7 @@ describe('CdkDrag', () => { fixture.detectChanges(); const dragElement = fixture.componentInstance.dragElement.nativeElement; - const cleanup = makePageScrollable(); + const cleanup = makeScrollable(); scrollTo(0, 500); expect(dragElement.style.transform).toBeFalsy(); @@ -2034,7 +2034,7 @@ describe('CdkDrag', () => { const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; const list = fixture.componentInstance.dropInstance.element.nativeElement; - const cleanup = makePageScrollable(); + const cleanup = makeScrollable(); scrollTo(0, 10); let listRect = list.getBoundingClientRect(); // Note that we need to measure after scrolling. @@ -2060,6 +2060,43 @@ describe('CdkDrag', () => { cleanup(); })); + it('should update the boundary if a parent is scrolled while dragging', fakeAsync(() => { + const fixture = createComponent(DraggableInScrollableParentContainer); + fixture.componentInstance.boundarySelector = '.cdk-drop-list'; + fixture.detectChanges(); + + const container: HTMLElement = fixture.nativeElement.querySelector('.container'); + const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; + const list = fixture.componentInstance.dropInstance.element.nativeElement; + const cleanup = makeScrollable('vertical', container); + container.scrollTop = 10; + let listRect = list.getBoundingClientRect(); // Note that we need to measure after scrolling. + + startDraggingViaMouse(fixture, item); + startDraggingViaMouse(fixture, item, listRect.right, listRect.bottom); + flush(); + dispatchMouseEvent(document, 'mousemove', listRect.right, listRect.bottom); + fixture.detectChanges(); + + const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement; + let previewRect = preview.getBoundingClientRect(); + + // Different browsers round the scroll position differently so + // assert that the offsets are within a pixel of each other. + expect(Math.abs(previewRect.bottom - listRect.bottom)).toBeLessThan(2); + + container.scrollTop = 0; + dispatchFakeEvent(container, 'scroll'); + fixture.detectChanges(); + listRect = list.getBoundingClientRect(); // We need to update these since we've scrolled. + dispatchMouseEvent(document, 'mousemove', listRect.right, listRect.bottom); + fixture.detectChanges(); + previewRect = preview.getBoundingClientRect(); + + expect(Math.abs(previewRect.bottom - listRect.bottom)).toBeLessThan(2); + cleanup(); + })); + it('should clear the id from the preview', fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); @@ -2375,7 +2412,7 @@ describe('CdkDrag', () => { fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); - const cleanup = makePageScrollable(); + const cleanup = makeScrollable(); scrollTo(0, 500); assertDownwardSorting(fixture, fixture.componentInstance.dragItems.map(item => { @@ -2396,7 +2433,7 @@ describe('CdkDrag', () => { fakeAsync(() => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); - const cleanup = makePageScrollable(); + const cleanup = makeScrollable(); scrollTo(0, 500); assertUpwardSorting(fixture, fixture.componentInstance.dragItems.map(item => { @@ -2893,7 +2930,7 @@ describe('CdkDrag', () => { 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 cleanup = makeScrollable(); const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement; startDraggingViaMouse(fixture, item, 50, 50); @@ -3485,7 +3522,7 @@ describe('CdkDrag', () => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); - const cleanup = makePageScrollable(); + const cleanup = makeScrollable(); const item = fixture.componentInstance.dragItems.first.element.nativeElement; const viewportRuler = TestBed.inject(ViewportRuler); const viewportSize = viewportRuler.getViewportSize(); @@ -3506,7 +3543,7 @@ describe('CdkDrag', () => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); - const cleanup = makePageScrollable(); + const cleanup = makeScrollable(); const item = fixture.componentInstance.dragItems.first.element.nativeElement; const viewportRuler = TestBed.inject(ViewportRuler); const viewportSize = viewportRuler.getViewportSize(); @@ -3529,7 +3566,7 @@ describe('CdkDrag', () => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); - const cleanup = makePageScrollable('horizontal'); + const cleanup = makeScrollable('horizontal'); const item = fixture.componentInstance.dragItems.first.element.nativeElement; const viewportRuler = TestBed.inject(ViewportRuler); const viewportSize = viewportRuler.getViewportSize(); @@ -3550,7 +3587,7 @@ describe('CdkDrag', () => { const fixture = createComponent(DraggableInDropZone); fixture.detectChanges(); - const cleanup = makePageScrollable('horizontal'); + const cleanup = makeScrollable('horizontal'); const item = fixture.componentInstance.dragItems.first.element.nativeElement; const viewportRuler = TestBed.inject(ViewportRuler); const viewportSize = viewportRuler.getViewportSize(); @@ -3587,7 +3624,7 @@ describe('CdkDrag', () => { list.style.margin = '0'; const listRect = list.getBoundingClientRect(); - const cleanup = makePageScrollable(); + const cleanup = makeScrollable(); scrollTo(0, viewportRuler.getViewportSize().height * 5); list.scrollTop = 50; @@ -3625,7 +3662,7 @@ describe('CdkDrag', () => { list.style.margin = '0'; const listRect = list.getBoundingClientRect(); - const cleanup = makePageScrollable(); + const cleanup = makeScrollable(); scrollTo(0, viewportRuler.getViewportSize().height * 5); list.scrollTop = 0; @@ -4744,7 +4781,7 @@ describe('CdkDrag', () => { fixture.detectChanges(); // Make the page scrollable and scroll the items out of view. - const cleanup = makePageScrollable(); + const cleanup = makeScrollable(); scrollTo(0, 4000); dispatchFakeEvent(document, 'scroll'); fixture.detectChanges(); @@ -5984,11 +6021,13 @@ 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(direction: 'vertical' | 'horizontal' = 'vertical') { +function makeScrollable( + direction: 'vertical' | 'horizontal' = 'vertical', + element = document.body) { const veryTallElement = document.createElement('div'); veryTallElement.style.width = direction === 'vertical' ? '100%' : '4000px'; veryTallElement.style.height = direction === 'vertical' ? '2000px' : '5px'; - document.body.appendChild(veryTallElement); + element.appendChild(veryTallElement); return () => { scrollTo(0, 0); diff --git a/src/cdk/drag-drop/drag-ref.ts b/src/cdk/drag-drop/drag-ref.ts index ee0c87a11e2e..d0726bd51e5f 100644 --- a/src/cdk/drag-drop/drag-ref.ts +++ b/src/cdk/drag-drop/drag-ref.ts @@ -12,12 +12,12 @@ 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'; import {getTransformTransitionDurationInMs} from './transition-duration'; import {getMutableClientRect, adjustClientRect} from './client-rect'; +import {ParentPositionTracker} from './parent-position-tracker'; /** Object that can be used to configure the behavior of DragRef. */ export interface DragRefConfig { @@ -136,8 +136,8 @@ export class DragRef { /** Index at which the item started in its initial container. */ private _initialIndex: number; - /** Cached scroll position on the page when the element was picked up. */ - private _scrollPosition: {top: number, left: number}; + /** Cached positions of scrollable parent elements. */ + private _parentPositions: ParentPositionTracker; /** Emits when the item is being moved. */ private _moveEvents = new Subject<{ @@ -305,6 +305,7 @@ export class DragRef { private _dragDropRegistry: DragDropRegistry) { this.withRootElement(element); + this._parentPositions = new ParentPositionTracker(_document, _viewportRuler); _dragDropRegistry.registerDragItem(this); } @@ -422,6 +423,7 @@ export class DragRef { this._disabledHandles.clear(); this._dropContainer = undefined; this._resizeSubscription.unsubscribe(); + this._parentPositions.clear(); this._boundaryElement = this._rootElement = this._placeholderTemplate = this._previewTemplate = this._anchor = null!; } @@ -702,7 +704,9 @@ export class DragRef { this._toggleNativeDragInteractions(); - if (this._dropContainer) { + const dropContainer = this._dropContainer; + + if (dropContainer) { const element = this._rootElement; const parent = element.parentNode!; const preview = this._preview = this._createPreviewElement(); @@ -718,12 +722,16 @@ export class DragRef { element.style.display = 'none'; this._document.body.appendChild(parent.replaceChild(placeholder, element)); getPreviewInsertionPoint(this._document).appendChild(preview); - this._dropContainer.start(); - this._initialContainer = this._dropContainer; - this._initialIndex = this._dropContainer.getItemIndex(this); + dropContainer.start(); + this._initialContainer = dropContainer; + this._initialIndex = dropContainer.getItemIndex(this); } else { this._initialContainer = this._initialIndex = undefined!; } + + // Important to run after we've called `start` on the parent container + // so that it has had time to resolve its scrollable parents. + this._parentPositions.cache(dropContainer ? dropContainer.getScrollableParents() : []); } /** @@ -775,8 +783,8 @@ export class DragRef { this._removeSubscriptions(); this._pointerMoveSubscription = this._dragDropRegistry.pointerMove.subscribe(this._pointerMove); this._pointerUpSubscription = this._dragDropRegistry.pointerUp.subscribe(this._pointerUp); - this._scrollSubscription = this._dragDropRegistry.scroll.pipe(startWith(null)).subscribe(() => { - this._updateOnScroll(); + this._scrollSubscription = this._dragDropRegistry.scroll.subscribe(scrollEvent => { + this._updateOnScroll(scrollEvent); }); if (this._boundaryElement) { @@ -1014,8 +1022,9 @@ export class DragRef { const handleElement = referenceElement === this._rootElement ? null : referenceElement; const referenceRect = handleElement ? handleElement.getBoundingClientRect() : elementRect; const point = isTouchEvent(event) ? event.targetTouches[0] : event; - const x = point.pageX - referenceRect.left - this._scrollPosition.left; - const y = point.pageY - referenceRect.top - this._scrollPosition.top; + const scrollPosition = this._getViewportScrollPosition(); + const x = point.pageX - referenceRect.left - scrollPosition.left; + const y = point.pageY - referenceRect.top - scrollPosition.top; return { x: referenceRect.left - elementRect.left + x, @@ -1027,10 +1036,11 @@ export class DragRef { private _getPointerPositionOnPage(event: MouseEvent | TouchEvent): Point { // `touches` will be empty for start/end events so we have to fall back to `changedTouches`. const point = isTouchEvent(event) ? (event.touches[0] || event.changedTouches[0]) : event; + const scrollPosition = this._getViewportScrollPosition(); return { - x: point.pageX - this._scrollPosition.left, - y: point.pageY - this._scrollPosition.top + x: point.pageX - scrollPosition.left, + y: point.pageY - scrollPosition.top }; } @@ -1148,6 +1158,7 @@ export class DragRef { /** Cleans up any cached element dimensions that we don't need after dragging has stopped. */ private _cleanupCachedDimensions() { this._boundaryRect = this._previewRect = undefined; + this._parentPositions.clear(); } /** @@ -1223,19 +1234,21 @@ export class DragRef { } /** Updates the internal state of the draggable element when scrolling has occurred. */ - private _updateOnScroll() { - const oldScrollPosition = this._scrollPosition; - const currentScrollPosition = this._viewportRuler.getViewportScrollPosition(); + private _updateOnScroll(event: Event) { + const scrollDifference = this._parentPositions.handleScroll(event); // ClientRect dimensions are based on the page's scroll position so // we have to update the cached boundary ClientRect if the user has scrolled. - if (oldScrollPosition && this._boundaryRect) { - const topDifference = oldScrollPosition.top - currentScrollPosition.top; - const leftDifference = oldScrollPosition.left - currentScrollPosition.left; - adjustClientRect(this._boundaryRect, topDifference, leftDifference); + if (this._boundaryRect && scrollDifference) { + adjustClientRect(this._boundaryRect, scrollDifference.top, scrollDifference.left); } + } - this._scrollPosition = currentScrollPosition; + /** Gets the scroll position of the viewport. */ + private _getViewportScrollPosition() { + const cachedPosition = this._parentPositions.positions.get(this._document); + return cachedPosition ? cachedPosition.scrollPosition : + this._viewportRuler.getViewportScrollPosition(); } } diff --git a/src/cdk/drag-drop/drop-list-ref.ts b/src/cdk/drag-drop/drop-list-ref.ts index 9e8ef001d610..29ff830aa72d 100644 --- a/src/cdk/drag-drop/drop-list-ref.ts +++ b/src/cdk/drag-drop/drop-list-ref.ts @@ -22,6 +22,7 @@ import { getMutableClientRect, isInsideClientRect, } from './client-rect'; +import {ParentPositionTracker} from './parent-position-tracker'; /** * Proximity, as a ratio to width/height, at which a @@ -54,12 +55,6 @@ 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} @@ -143,11 +138,8 @@ export class DropListRef { /** Cache of the dimensions of all the items inside the container. */ private _itemPositions: CachedItemPosition[] = []; - /** Cached positions of the scrollable parent elements. */ - private _parentPositions = new Map(); + /** Keeps track of the positions of any parent scrollable elements. */ + private _parentPositions: ParentPositionTracker; /** Cached `ClientRect` of the drop list. */ private _clientRect: ClientRect; @@ -217,6 +209,7 @@ export class DropListRef { this._document = _document; this.withScrollableParents([this.element]); _dragDropRegistry.registerDropContainer(this); + this._parentPositions = new ParentPositionTracker(_document, _viewportRuler); } /** Removes the drop list functionality from the DOM element. */ @@ -425,6 +418,11 @@ export class DropListRef { return this; } + /** Gets the scrollable parents that are registered with this drop container. */ + getScrollableParents(): ReadonlyArray { + return this._scrollableElements; + } + /** * Figures out the index of an item in the container. * @param item Item whose index should be determined. @@ -549,7 +547,7 @@ export class DropListRef { let horizontalScrollDirection = AutoScrollHorizontalDirection.NONE; // Check whether we should start scrolling any of the parent containers. - this._parentPositions.forEach((position, element) => { + this._parentPositions.positions.forEach((position, element) => { // We have special handling for the `document` below. Also this would be // nicer with a for...of loop, but it requires changing a compiler flag. if (element === this._document || !position.clientRect || scrollNode) { @@ -598,25 +596,12 @@ export class DropListRef { /** Caches the positions of the configured scrollable parents. */ private _cacheParentPositions() { - this._parentPositions.clear(); - this._parentPositions.set(this._document, { - scrollPosition: this._viewportRuler!.getViewportScrollPosition(), - }); - this._scrollableElements.forEach(element => { - const clientRect = getMutableClientRect(element); - - // We keep the ClientRect cached in two properties, because it's referenced in a lot of - // performance-sensitive places and we want to avoid the extra lookups. The `element` is - // guaranteed to always be in the `_scrollableElements` so this should always match. - if (element === this.element) { - this._clientRect = clientRect; - } + const element = coerceElement(this.element); + this._parentPositions.cache(this._scrollableElements); - this._parentPositions.set(element, { - scrollPosition: {top: element.scrollTop, left: element.scrollLeft}, - clientRect - }); - }); + // The list element is always in the `scrollableElements` + // so we can take advantage of the cached `ClientRect`. + this._clientRect = this._parentPositions.positions.get(element)!.clientRect!; } /** Refreshes the position cache of the items and sibling containers. */ @@ -753,53 +738,6 @@ export class DropListRef { this._cacheParentPositions(); } - /** - * Updates the internal state of the container after a scroll event has happened. - * @param scrolledParent Element that was scrolled. - * @param newTop New top scroll position. - * @param newLeft New left scroll position. - */ - private _updateAfterScroll(scrolledParent: HTMLElement | Document, - newTop: number, - newLeft: number) { - // Used when figuring out whether an element is inside the scroll parent. If the scrolled - // parent is the `document`, we use the `documentElement`, because IE doesn't support `contains` - // on the `document`. - const scrolledParentNode = - scrolledParent === this._document ? scrolledParent.documentElement : scrolledParent; - const scrollPosition = this._parentPositions.get(scrolledParent)!.scrollPosition; - const topDifference = scrollPosition.top - newTop; - const leftDifference = scrollPosition.left - newLeft; - - // Go through and update the cached positions of the scroll - // parents that are inside the element that was scrolled. - this._parentPositions.forEach((position, node) => { - if (position.clientRect && scrolledParent !== node && scrolledParentNode.contains(node)) { - adjustClientRect(position.clientRect, 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; - } - /** Starts the interval that'll auto-scroll the element. */ private _startScrollInterval = () => { this._stopScrolling(); @@ -903,23 +841,26 @@ export class DropListRef { private _listenToScrollEvents() { this._viewportScrollSubscription = this._dragDropRegistry.scroll.subscribe(event => { if (this.isDragging()) { - const target = event.target as HTMLElement | Document; - const position = this._parentPositions.get(target); - - if (position) { - let newTop: number; - let newLeft: number; - - if (target === this._document) { - const scrollPosition = this._viewportRuler!.getViewportScrollPosition(); - newTop = scrollPosition.top; - newLeft = scrollPosition.left; - } else { - newTop = (target as HTMLElement).scrollTop; - newLeft = (target as HTMLElement).scrollLeft; - } - - this._updateAfterScroll(target, newTop, newLeft); + const scrollDifference = this._parentPositions.handleScroll(event); + + if (scrollDifference) { + // 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, scrollDifference.top, scrollDifference.left); + }); + + // 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(); + } + }); } } else if (this.isReceiving()) { this._cacheParentPositions(); diff --git a/src/cdk/drag-drop/parent-position-tracker.ts b/src/cdk/drag-drop/parent-position-tracker.ts new file mode 100644 index 000000000000..f385b4df98e8 --- /dev/null +++ b/src/cdk/drag-drop/parent-position-tracker.ts @@ -0,0 +1,90 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {ViewportRuler} from '@angular/cdk/scrolling'; +import {getMutableClientRect, adjustClientRect} from './client-rect'; + +/** Object holding the scroll position of something. */ +interface ScrollPosition { + top: number; + left: number; +} + +/** Keeps track of the scroll position and dimensions of the parents of an element. */ +export class ParentPositionTracker { + /** Cached positions of the scrollable parent elements. */ + readonly positions = new Map(); + + constructor(private _document: Document, private _viewportRuler: ViewportRuler) {} + + /** Clears the cached positions. */ + clear() { + this.positions.clear(); + } + + /** Caches the positions. Should be called at the beginning of a drag sequence. */ + cache(elements: HTMLElement[] | ReadonlyArray) { + this.clear(); + this.positions.set(this._document, { + scrollPosition: this._viewportRuler.getViewportScrollPosition(), + }); + + elements.forEach(element => { + this.positions.set(element, { + scrollPosition: {top: element.scrollTop, left: element.scrollLeft}, + clientRect: getMutableClientRect(element) + }); + }); + } + + /** Handles scrolling while a drag is taking place. */ + handleScroll(event: Event): ScrollPosition | null { + const target = event.target as HTMLElement | Document; + const cachedPosition = this.positions.get(target); + + if (!cachedPosition) { + return null; + } + + // Used when figuring out whether an element is inside the scroll parent. If the scrolled + // parent is the `document`, we use the `documentElement`, because IE doesn't support + // `contains` on the `document`. + const scrolledParentNode = target === this._document ? target.documentElement : target; + const scrollPosition = cachedPosition.scrollPosition; + let newTop: number; + let newLeft: number; + + if (target === this._document) { + const viewportScrollPosition = this._viewportRuler!.getViewportScrollPosition(); + newTop = viewportScrollPosition.top; + newLeft = viewportScrollPosition.left; + } else { + newTop = (target as HTMLElement).scrollTop; + newLeft = (target as HTMLElement).scrollLeft; + } + + const topDifference = scrollPosition.top - newTop; + const leftDifference = scrollPosition.left - newLeft; + + // Go through and update the cached positions of the scroll + // parents that are inside the element that was scrolled. + this.positions.forEach((position, node) => { + if (position.clientRect && target !== node && scrolledParentNode.contains(node)) { + adjustClientRect(position.clientRect, topDifference, leftDifference); + } + }); + + scrollPosition.top = newTop; + scrollPosition.left = newLeft; + + return {top: topDifference, left: leftDifference}; + } +} diff --git a/tools/public_api_guard/cdk/drag-drop.d.ts b/tools/public_api_guard/cdk/drag-drop.d.ts index 87cab847d822..527cbdc5b45d 100644 --- a/tools/public_api_guard/cdk/drag-drop.d.ts +++ b/tools/public_api_guard/cdk/drag-drop.d.ts @@ -379,6 +379,7 @@ export declare class DropListRef { enter(item: DragRef, pointerX: number, pointerY: number, index?: number): void; exit(item: DragRef): void; getItemIndex(item: DragRef): number; + getScrollableParents(): ReadonlyArray; isDragging(): boolean; isReceiving(): boolean; start(): void;