Skip to content

Commit

Permalink
fix(cdk/drag-drop): incorrectly sorting element inside dialog with bl…
Browse files Browse the repository at this point in the history
…ocked scrolling (#14806)

We use the `ViewportRuler` to determine how much a page has been scrolled in order to compensate for it in some of the `ClientRect`-related calculations. This breaks down when the drop list is inside a dialog with blocked scrolling, because scroll blocking works by offsetting the `html` node which in turn throws off the `ViewportRuler`. These changes switches to reading the values off the `window` which won't be thrown off.

Fixes #14280.
  • Loading branch information
crisbeto committed Feb 23, 2022
1 parent feaf50b commit 3e1de9d
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 10 deletions.
19 changes: 19 additions & 0 deletions src/cdk/drag-drop/directives/drag.spec.ts
Expand Up @@ -4903,6 +4903,25 @@ describe('CdkDrag', () => {
.withContext('Expected placeholder to preserve transform when dragging stops.')
.toBe(true);
}));

it('should sort correctly if the <html> node has been offset', fakeAsync(() => {
const documentElement = document.documentElement!;
const fixture = createComponent(DraggableInDropZone);
fixture.detectChanges();

documentElement.style.position = 'absolute';
documentElement.style.top = '-100px';

assertDownwardSorting(
fixture,
fixture.componentInstance.dragItems.map(item => {
return item.element.nativeElement;
}),
);

documentElement.style.position = '';
documentElement.style.top = '';
}));
});

describe('in a connected drop container', () => {
Expand Down
10 changes: 5 additions & 5 deletions src/cdk/drag-drop/drag-ref.ts
Expand Up @@ -363,7 +363,7 @@ export class DragRef<T = any> {
private _dragDropRegistry: DragDropRegistry<DragRef, DropListRef>,
) {
this.withRootElement(element).withParent(_config.parentDragRef || null);
this._parentPositions = new ParentPositionTracker(_document, _viewportRuler);
this._parentPositions = new ParentPositionTracker(_document);
_dragDropRegistry.registerDragItem(this);
}

Expand Down Expand Up @@ -1461,10 +1461,10 @@ export class DragRef<T = any> {

/** Gets the scroll position of the viewport. */
private _getViewportScrollPosition() {
const cachedPosition = this._parentPositions.positions.get(this._document);
return cachedPosition
? cachedPosition.scrollPosition
: this._viewportRuler.getViewportScrollPosition();
return (
this._parentPositions.positions.get(this._document)?.scrollPosition ||
this._parentPositions.getViewportScrollPosition()
);
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/cdk/drag-drop/drop-list-ref.ts
Expand Up @@ -230,7 +230,7 @@ export class DropListRef<T = any> {
this._document = _document;
this.withScrollableParents([this.element]);
_dragDropRegistry.registerDropContainer(this);
this._parentPositions = new ParentPositionTracker(_document, _viewportRuler);
this._parentPositions = new ParentPositionTracker(_document);
}

/** Removes the drop list functionality from the DOM element. */
Expand Down
17 changes: 13 additions & 4 deletions src/cdk/drag-drop/parent-position-tracker.ts
Expand Up @@ -6,7 +6,6 @@
* found in the LICENSE file at https://angular.io/license
*/

import {ViewportRuler} from '@angular/cdk/scrolling';
import {_getEventTarget} from '@angular/cdk/platform';
import {getMutableClientRect, adjustClientRect} from './client-rect';

Expand All @@ -27,7 +26,7 @@ export class ParentPositionTracker {
}
>();

constructor(private _document: Document, private _viewportRuler: ViewportRuler) {}
constructor(private _document: Document) {}

/** Clears the cached positions. */
clear() {
Expand All @@ -38,7 +37,7 @@ export class ParentPositionTracker {
cache(elements: readonly HTMLElement[]) {
this.clear();
this.positions.set(this._document, {
scrollPosition: this._viewportRuler.getViewportScrollPosition(),
scrollPosition: this.getViewportScrollPosition(),
});

elements.forEach(element => {
Expand All @@ -63,7 +62,7 @@ export class ParentPositionTracker {
let newLeft: number;

if (target === this._document) {
const viewportScrollPosition = this._viewportRuler!.getViewportScrollPosition();
const viewportScrollPosition = this.getViewportScrollPosition();
newTop = viewportScrollPosition.top;
newLeft = viewportScrollPosition.left;
} else {
Expand All @@ -87,4 +86,14 @@ export class ParentPositionTracker {

return {top: topDifference, left: leftDifference};
}

/**
* Gets the scroll position of the viewport. Note that we use the scrollX and scrollY directly,
* instead of going through the `ViewportRuler`, because the first value the ruler looks at is
* the top/left offset of the `document.documentElement` which works for most cases, but breaks
* if the element is offset by something like the `BlockScrollStrategy`.
*/
getViewportScrollPosition() {
return {top: window.scrollY, left: window.scrollX};
}
}

0 comments on commit 3e1de9d

Please sign in to comment.