Skip to content

Commit cf61960

Browse files
authored
feat(cdk/drag-drop): introduce resetToBoundary (#30436)
this commit introduces `resetToBoundary` in DragRef which allows user to align DragItem to its boundary on demand if at one point it was at a place where the boundary element used to be and has shrinked causing DragItem to be outside of the boundary box fixes #30325
1 parent 46344d8 commit cf61960

File tree

5 files changed

+181
-1
lines changed

5 files changed

+181
-1
lines changed

goldens/cdk/drag-drop/index.api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
8888
_resetPlaceholderTemplate(placeholder: CdkDragPlaceholder): void;
8989
// (undocumented)
9090
_resetPreviewTemplate(preview: CdkDragPreview): void;
91+
resetToBoundary(): void;
9192
rootElementSelector: string;
9293
scale: number;
9394
setFreeDragPosition(value: Point): void;
@@ -439,6 +440,7 @@ export class DragRef<T = any> {
439440
event: MouseEvent | TouchEvent;
440441
}>;
441442
reset(): void;
443+
resetToBoundary(): void;
442444
scale: number;
443445
setFreeDragPosition(value: Point): this;
444446
_sortFromLastPointerPosition(): void;

src/cdk/drag-drop/directives/drag.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,11 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
279279
this._dragRef.reset();
280280
}
281281

282+
/** Resets drag item to end of boundary element. */
283+
resetToBoundary() {
284+
this._dragRef.resetToBoundary();
285+
}
286+
282287
/**
283288
* Gets the pixel coordinates of the draggable outside of a drop container.
284289
*/

src/cdk/drag-drop/directives/standalone-drag.spec.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
startDraggingViaMouse,
3434
startDraggingViaTouch,
3535
} from './test-utils.spec';
36+
import {isInsideClientRect, isOverflowingParent} from '../dom/dom-rect';
3637

3738
describe('Standalone CdkDrag', () => {
3839
describe('mouse dragging', () => {
@@ -46,6 +47,95 @@ describe('Standalone CdkDrag', () => {
4647
expect(dragElement.style.transform).toBe('translate3d(50px, 100px, 0px)');
4748
}));
4849

50+
it('should reset drag item to boundary', fakeAsync(() => {
51+
const fixture = createComponent(DragWithResizeableBoundary);
52+
fixture.detectChanges();
53+
let boundaryElement = fixture.componentInstance.boundaryElement.nativeElement;
54+
let dragElement = fixture.componentInstance.dragElement.nativeElement;
55+
56+
dragElementViaMouse(fixture, dragElement, 50, 100);
57+
58+
// check if the drag element is within the boundary or not
59+
expect(
60+
isInsideClientRect(
61+
boundaryElement.getBoundingClientRect(),
62+
fixture.componentInstance.dragInstance.getFreeDragPosition().x,
63+
fixture.componentInstance.dragInstance.getFreeDragPosition().y,
64+
),
65+
).toBeTrue();
66+
67+
// drag it till the end of the boundary
68+
dragElementViaMouse(fixture, dragElement, 400, 400);
69+
70+
// it should still be present within the boundary
71+
expect(
72+
isInsideClientRect(
73+
boundaryElement.getBoundingClientRect(),
74+
fixture.componentInstance.dragInstance.getFreeDragPosition().x,
75+
fixture.componentInstance.dragInstance.getFreeDragPosition().y,
76+
),
77+
).toBeTrue();
78+
79+
// shrink boundary to check if we are within boundary or not
80+
fixture.componentInstance.setBoundary('200px', '200px');
81+
fixture.detectChanges();
82+
83+
// it should not be within the boundary anymore as we shrinked it
84+
expect(
85+
isInsideClientRect(
86+
boundaryElement.getBoundingClientRect(),
87+
fixture.componentInstance.dragInstance.getFreeDragPosition().x,
88+
fixture.componentInstance.dragInstance.getFreeDragPosition().y,
89+
),
90+
).toBeFalse();
91+
92+
fixture.componentInstance.dragInstance.resetToBoundary();
93+
fixture.detectChanges();
94+
95+
// should be be within bounding box of its boundary now that we have reseted it
96+
expect(
97+
isInsideClientRect(
98+
boundaryElement.getBoundingClientRect(),
99+
fixture.componentInstance.dragInstance.getFreeDragPosition().x,
100+
fixture.componentInstance.dragInstance.getFreeDragPosition().y,
101+
),
102+
).toBeTrue();
103+
104+
// expand the boundary enough that so can we can make the draggable item to be overflown
105+
// of its parent from top side
106+
fixture.componentInstance.setBoundary('500px', '500px');
107+
fixture.detectChanges();
108+
109+
// drag it till the end of the boundary
110+
dragElementViaMouse(fixture, dragElement, 500, 500);
111+
112+
// shrink boundary to make draggable item to be overflown
113+
fixture.componentInstance.setBoundary('400px', '400px');
114+
fixture.detectChanges();
115+
116+
// should be within bounding rect but it's overflowing as it was placed in a way that
117+
// it is overflowing
118+
expect(
119+
isOverflowingParent(
120+
boundaryElement.getBoundingClientRect(),
121+
dragElement.getBoundingClientRect(),
122+
),
123+
).toBeTrue();
124+
125+
// reset it so that overflowing offset is fixed
126+
fixture.componentInstance.dragInstance.resetToBoundary();
127+
fixture.detectChanges();
128+
129+
// should be within bounding rect but it's overflowing as it was placed in a way that
130+
// it is overflowing
131+
expect(
132+
isOverflowingParent(
133+
boundaryElement.getBoundingClientRect(),
134+
dragElement.getBoundingClientRect(),
135+
),
136+
).toBeFalse();
137+
}));
138+
49139
it('should drag an element freely to a particular position when the page is scrolled', fakeAsync(() => {
50140
const fixture = createComponent(StandaloneDraggable);
51141
fixture.detectChanges();
@@ -2047,3 +2137,25 @@ class PlainStandaloneDraggable {
20472137
class StandaloneDraggableWithExternalTemplateHandle {
20482138
@ViewChild('dragElement') dragElement: ElementRef<HTMLElement>;
20492139
}
2140+
2141+
@Component({
2142+
template: `
2143+
<div #boundaryElement class="example-boundary" style="width: 400px; height: 400px">
2144+
<div #dragElement class="example-box" cdkDragBoundary=".example-boundary" cdkDrag style="width: 100px; height: 100px">
2145+
I can only be dragged within the container
2146+
</div>
2147+
</div>
2148+
`,
2149+
imports: [CdkDrag],
2150+
})
2151+
class DragWithResizeableBoundary {
2152+
@ViewChild('boundaryElement') boundaryElement: ElementRef<HTMLElement>;
2153+
2154+
@ViewChild('dragElement') dragElement: ElementRef<HTMLElement>;
2155+
@ViewChild(CdkDrag) dragInstance: CdkDrag;
2156+
2157+
setBoundary(height: string, width: string) {
2158+
this.boundaryElement.nativeElement.style.height = height;
2159+
this.boundaryElement.nativeElement.style.width = width;
2160+
}
2161+
}

src/cdk/drag-drop/dom/dom-rect.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,23 @@ export function isInsideClientRect(clientRect: DOMRect, x: number, y: number) {
3737
return y >= top && y <= bottom && x >= left && x <= right;
3838
}
3939

40+
/**
41+
* Checks if the child element is overflowing from its parent.
42+
* @param parentRect - The bounding rect of the parent element.
43+
* @param childRect - The bounding rect of the child element.
44+
*/
45+
export function isOverflowingParent(parentRect: DOMRect, childRect: DOMRect): boolean {
46+
// check for horizontal overflow (left and right)
47+
const isLeftOverflowing = childRect.left < parentRect.left;
48+
const isRightOverflowing = childRect.left + childRect.width > parentRect.right;
49+
50+
// check for vertical overflow (top and bottom)
51+
const isTopOverflowing = childRect.top < parentRect.top;
52+
const isBottomOverflowing = childRect.top + childRect.height > parentRect.bottom;
53+
54+
return isLeftOverflowing || isRightOverflowing || isTopOverflowing || isBottomOverflowing;
55+
}
56+
4057
/**
4158
* Updates the top/left positions of a `DOMRect`, as well as their bottom/right counterparts.
4259
* @param domRect `DOMRect` that should be updated.

src/cdk/drag-drop/drag-ref.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import {
2222
} from '@angular/core';
2323
import {Observable, Subject, Subscription} from 'rxjs';
2424
import {deepCloneNode} from './dom/clone-node';
25-
import {adjustDomRect, getMutableClientRect} from './dom/dom-rect';
25+
import {adjustDomRect, getMutableClientRect, isOverflowingParent} from './dom/dom-rect';
2626
import {ParentPositionTracker} from './dom/parent-position-tracker';
2727
import {getRootNode} from './dom/root-node';
2828
import {
@@ -546,6 +546,50 @@ export class DragRef<T = any> {
546546
this._passiveTransform = {x: 0, y: 0};
547547
}
548548

549+
/** Resets drag item to end of boundary element. */
550+
resetToBoundary(): void {
551+
if (
552+
// can be null if the drag item was never dragged.
553+
this._boundaryElement &&
554+
this._rootElement &&
555+
// check if we are overflowing off our boundary element
556+
isOverflowingParent(
557+
this._boundaryElement.getBoundingClientRect(),
558+
this._rootElement.getBoundingClientRect(),
559+
)
560+
) {
561+
const parentRect = this._boundaryElement.getBoundingClientRect();
562+
const childRect = this._rootElement.getBoundingClientRect();
563+
564+
let offsetX = 0;
565+
let offsetY = 0;
566+
567+
// check if we are overflowing from left or right
568+
if (childRect.left < parentRect.left) {
569+
offsetX = parentRect.left - childRect.left;
570+
} else if (childRect.right > parentRect.right) {
571+
offsetX = parentRect.right - childRect.right;
572+
}
573+
574+
// check if we are overflowing from top or bottom
575+
if (childRect.top < parentRect.top) {
576+
offsetY = parentRect.top - childRect.top;
577+
} else if (childRect.bottom > parentRect.bottom) {
578+
offsetY = parentRect.bottom - childRect.bottom;
579+
}
580+
581+
const currentLeft = this._activeTransform.x;
582+
const currentTop = this._activeTransform.y;
583+
584+
let x = currentLeft + offsetX,
585+
y = currentTop + offsetY;
586+
587+
this._rootElement.style.transform = getTransform(x, y);
588+
this._activeTransform = {x, y};
589+
this._passiveTransform = {x, y};
590+
}
591+
}
592+
549593
/**
550594
* Sets a handle as disabled. While a handle is disabled, it'll capture and interrupt dragging.
551595
* @param handle Handle element that should be disabled.

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy