Skip to content

Commit

Permalink
fix(cdk/drag-drop): auto-scroll to the left not starting in rtl layout (
Browse files Browse the repository at this point in the history
#28334)

The drop list has some logic that prevents auto-scrolling when the container isn't scrollable. It didn't account for RTL which meant that, even though the rest of the logic works in RTL, auto-scrolling was being disabled incorrectly.

Fixes #28326.

(cherry picked from commit 6db225e)
  • Loading branch information
crisbeto committed Jan 2, 2024
1 parent c3cfa38 commit 82c37a9
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 6 deletions.
54 changes: 52 additions & 2 deletions src/cdk/drag-drop/directives/drag.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4352,7 +4352,7 @@ describe('CdkDrag', () => {
expect(list.scrollTop).toBeLessThan(initialScrollDistance);
}));

it('should auto-scroll right if the user holds their pointer at right edge', fakeAsync(() => {
it('should auto-scroll right if the user holds their pointer at right edge in ltr', fakeAsync(() => {
const fixture = createComponent(DraggableInScrollableHorizontalDropZone);
fixture.detectChanges();
const item = fixture.componentInstance.dragItems.first.element.nativeElement;
Expand All @@ -4374,7 +4374,7 @@ describe('CdkDrag', () => {
expect(list.scrollLeft).toBeGreaterThan(0);
}));

it('should auto-scroll left if the user holds their pointer at left edge', fakeAsync(() => {
it('should auto-scroll left if the user holds their pointer at left edge in ltr', fakeAsync(() => {
const fixture = createComponent(DraggableInScrollableHorizontalDropZone);
fixture.detectChanges();
const item = fixture.componentInstance.dragItems.first.element.nativeElement;
Expand All @@ -4390,6 +4390,56 @@ describe('CdkDrag', () => {
expect(list.scrollLeft).toBeLessThan(initialScrollDistance);
}));

it('should auto-scroll right if the user holds their pointer at right edge in rtl', fakeAsync(() => {
const fixture = createComponent(DraggableInScrollableHorizontalDropZone, [
{
provide: Directionality,
useValue: {value: 'rtl', change: observableOf()},
},
]);
fixture.nativeElement.setAttribute('dir', 'rtl');
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.width,
listRect.top + listRect.height / 2,
);
fixture.detectChanges();
tickAnimationFrames(20);

expect(list.scrollLeft).toBeGreaterThan(initialScrollDistance);
}));

it('should auto-scroll left if the user holds their pointer at left edge in rtl', fakeAsync(() => {
const fixture = createComponent(DraggableInScrollableHorizontalDropZone, [
{
provide: Directionality,
useValue: {value: 'rtl', change: observableOf()},
},
]);
fixture.nativeElement.setAttribute('dir', 'rtl');
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.top + listRect.height / 2);
fixture.detectChanges();
tickAnimationFrames(20);

expect(list.scrollLeft).toBeLessThan(0);
}));

it('should be able to start auto scrolling with a drag boundary', fakeAsync(() => {
const fixture = createComponent(DraggableInScrollableHorizontalDropZone);
fixture.componentInstance.boundarySelector = '.drop-list';
Expand Down
22 changes: 18 additions & 4 deletions src/cdk/drag-drop/drop-list-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ export class DropListRef<T = any> {
[verticalScrollDirection, horizontalScrollDirection] = getElementScrollDirections(
element as HTMLElement,
position.clientRect,
this._sortStrategy.direction,
pointerX,
pointerY,
);
Expand Down Expand Up @@ -746,12 +747,14 @@ function getHorizontalScrollDirection(clientRect: DOMRect, pointerX: number) {
* assuming that the user's pointer is already within it scrollable region.
* @param element Element for which we should calculate the scroll direction.
* @param clientRect Bounding client rectangle of the element.
* @param direction Layout direction of the drop list.
* @param pointerX Position of the user's pointer along the x axis.
* @param pointerY Position of the user's pointer along the y axis.
*/
function getElementScrollDirections(
element: HTMLElement,
clientRect: DOMRect,
direction: Direction,
pointerX: number,
pointerY: number,
): [AutoScrollVerticalDirection, AutoScrollHorizontalDirection] {
Expand Down Expand Up @@ -779,12 +782,23 @@ function getElementScrollDirections(
if (computedHorizontal) {
const scrollLeft = element.scrollLeft;

if (computedHorizontal === AutoScrollHorizontalDirection.LEFT) {
if (scrollLeft > 0) {
if (direction === 'rtl') {
if (computedHorizontal === AutoScrollHorizontalDirection.RIGHT) {
// In RTL `scrollLeft` will be negative when scrolled.
if (scrollLeft < 0) {
horizontalScrollDirection = AutoScrollHorizontalDirection.RIGHT;
}
} else if (element.scrollWidth + scrollLeft > element.clientWidth) {
horizontalScrollDirection = AutoScrollHorizontalDirection.LEFT;
}
} else if (element.scrollWidth - scrollLeft > element.clientWidth) {
horizontalScrollDirection = AutoScrollHorizontalDirection.RIGHT;
} else {
if (computedHorizontal === AutoScrollHorizontalDirection.LEFT) {
if (scrollLeft > 0) {
horizontalScrollDirection = AutoScrollHorizontalDirection.LEFT;
}
} else if (element.scrollWidth - scrollLeft > element.clientWidth) {
horizontalScrollDirection = AutoScrollHorizontalDirection.RIGHT;
}
}
}

Expand Down

0 comments on commit 82c37a9

Please sign in to comment.