Skip to content

Commit 4704653

Browse files
crisbetojosephperrott
authored andcommitted
feat(drag-drop): add the ability to customize how the position is constrained (#15137)
1 parent 9d60a5d commit 4704653

File tree

4 files changed

+77
-8
lines changed

4 files changed

+77
-8
lines changed

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

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import {CdkDropList} from './drop-list';
3030
import {CdkDragHandle} from './drag-handle';
3131
import {CdkDropListGroup} from './drop-list-group';
3232
import {extendStyles} from '../drag-styling';
33-
import {DragRefConfig} from '../drag-ref';
33+
import {DragRefConfig, Point} from '../drag-ref';
3434

3535
const ITEM_HEIGHT = 25;
3636
const ITEM_WIDTH = 75;
@@ -697,6 +697,24 @@ describe('CdkDrag', () => {
697697
expect(dragElement.style.transform).toBe('translate3d(100px, 100px, 0px)');
698698
}));
699699

700+
it('should allow for the position constrain logic to be customized', fakeAsync(() => {
701+
const fixture = createComponent(StandaloneDraggable);
702+
const spy = jasmine.createSpy('constrain position spy').and.returnValue({
703+
x: 50,
704+
y: 50
705+
} as Point);
706+
707+
fixture.componentInstance.constrainPosition = spy;
708+
fixture.detectChanges();
709+
const dragElement = fixture.componentInstance.dragElement.nativeElement;
710+
711+
expect(dragElement.style.transform).toBeFalsy();
712+
dragElementViaMouse(fixture, dragElement, 300, 300);
713+
714+
expect(spy).toHaveBeenCalledWith(jasmine.objectContaining({x: 300, y: 300}));
715+
expect(dragElement.style.transform).toBe('translate3d(50px, 50px, 0px)');
716+
}));
717+
700718
it('should throw if attached to an ng-container', fakeAsync(() => {
701719
expect(() => {
702720
createComponent(DraggableOnNgContainer).detectChanges();
@@ -2105,6 +2123,33 @@ describe('CdkDrag', () => {
21052123
expect(Math.floor(previewRect.right)).toBe(Math.floor(listRect.right));
21062124
}));
21072125

2126+
it('should be able to constrain the preview position with a custom function', fakeAsync(() => {
2127+
const fixture = createComponent(DraggableInDropZoneWithCustomPreview);
2128+
const spy = jasmine.createSpy('constrain position spy').and.returnValue({
2129+
x: 50,
2130+
y: 50
2131+
} as Point);
2132+
2133+
fixture.componentInstance.constrainPosition = spy;
2134+
fixture.detectChanges();
2135+
const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement;
2136+
2137+
startDraggingViaMouse(fixture, item);
2138+
2139+
const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement;
2140+
2141+
startDraggingViaMouse(fixture, item, 200, 200);
2142+
flush();
2143+
dispatchMouseEvent(document, 'mousemove', 200, 200);
2144+
fixture.detectChanges();
2145+
2146+
const previewRect = preview.getBoundingClientRect();
2147+
2148+
expect(spy).toHaveBeenCalledWith(jasmine.objectContaining({x: 200, y: 200}));
2149+
expect(Math.floor(previewRect.top)).toBe(50);
2150+
expect(Math.floor(previewRect.left)).toBe(50);
2151+
}));
2152+
21082153
it('should revert the element back to its parent after dragging with a custom ' +
21092154
'preview has stopped', fakeAsync(() => {
21102155
const fixture = createComponent(DraggableInDropZoneWithCustomPreview);
@@ -3049,6 +3094,7 @@ describe('CdkDrag', () => {
30493094
cdkDrag
30503095
[cdkDragBoundary]="boundarySelector"
30513096
[cdkDragStartDelay]="dragStartDelay"
3097+
[cdkDragConstrainPosition]="constrainPosition"
30523098
(cdkDragStarted)="startedSpy($event)"
30533099
(cdkDragReleased)="releasedSpy($event)"
30543100
(cdkDragEnded)="endedSpy($event)"
@@ -3065,6 +3111,7 @@ class StandaloneDraggable {
30653111
releasedSpy = jasmine.createSpy('released spy');
30663112
boundarySelector: string;
30673113
dragStartDelay: number;
3114+
constrainPosition: (point: Point) => Point;
30683115
}
30693116

30703117
@Component({
@@ -3263,6 +3310,7 @@ class DraggableInHorizontalDropZone {
32633310
<div
32643311
*ngFor="let item of items"
32653312
cdkDrag
3313+
[cdkDragConstrainPosition]="constrainPosition"
32663314
[cdkDragBoundary]="boundarySelector"
32673315
style="width: 100%; height: ${ITEM_HEIGHT}px; background: red;">
32683316
{{item}}
@@ -3283,6 +3331,7 @@ class DraggableInDropZoneWithCustomPreview {
32833331
items = ['Zero', 'One', 'Two', 'Three'];
32843332
boundarySelector: string;
32853333
renderCustomPreview = true;
3334+
constrainPosition: (point: Point) => Point;
32863335
}
32873336

32883337

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import {CdkDragPlaceholder} from './drag-placeholder';
4848
import {CdkDragPreview} from './drag-preview';
4949
import {CDK_DROP_LIST} from '../drop-list-container';
5050
import {CDK_DRAG_PARENT} from '../drag-parent';
51-
import {DragRef, DragRefConfig} from '../drag-ref';
51+
import {DragRef, DragRefConfig, Point} from '../drag-ref';
5252
import {DropListRef} from '../drop-list-ref';
5353
import {CdkDropListInternal as CdkDropList} from './drop-list';
5454
import {DragDrop} from '../drag-drop';
@@ -127,6 +127,14 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
127127
}
128128
private _disabled = false;
129129

130+
/**
131+
* Function that can be used to customize the logic of how the position of the drag item
132+
* is limited while it's being dragged. Gets called with a point containing the current position
133+
* of the user's pointer on the page and should return a point describing where the item should
134+
* be rendered.
135+
*/
136+
@Input('cdkDragConstrainPosition') constrainPosition?: (point: Point) => Point;
137+
130138
/** Emits when the user starts dragging the item. */
131139
@Output('cdkDragStarted') started: EventEmitter<CdkDragStart> = new EventEmitter<CdkDragStart>();
132140

@@ -310,6 +318,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
310318
ref.disabled = this.disabled;
311319
ref.lockAxis = this.lockAxis;
312320
ref.dragStartDelay = this.dragStartDelay;
321+
ref.constrainPosition = this.constrainPosition;
313322
ref
314323
.withBoundaryElement(this._getBoundaryElement())
315324
.withPlaceholderTemplate(placeholder)

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

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,14 @@ export class DragRef<T = any> {
273273
/** Arbitrary data that can be attached to the drag item. */
274274
data: T;
275275

276+
/**
277+
* Function that can be used to customize the logic of how the position of the drag item
278+
* is limited while it's being dragged. Gets called with a point containing the current position
279+
* of the user's pointer on the page and should return a point describing where the item should
280+
* be rendered.
281+
*/
282+
constrainPosition?: (point: Point) => Point;
283+
276284
constructor(
277285
element: ElementRef<HTMLElement> | HTMLElement,
278286
private _config: DragRefConfig,
@@ -909,12 +917,13 @@ export class DragRef<T = any> {
909917
/** Gets the pointer position on the page, accounting for any position constraints. */
910918
private _getConstrainedPointerPosition(event: MouseEvent | TouchEvent): Point {
911919
const point = this._getPointerPositionOnPage(event);
920+
const constrainedPoint = this.constrainPosition ? this.constrainPosition(point) : point;
912921
const dropContainerLock = this._dropContainer ? this._dropContainer.lockAxis : null;
913922

914923
if (this.lockAxis === 'x' || dropContainerLock === 'x') {
915-
point.y = this._pickupPositionOnPage.y;
924+
constrainedPoint.y = this._pickupPositionOnPage.y;
916925
} else if (this.lockAxis === 'y' || dropContainerLock === 'y') {
917-
point.x = this._pickupPositionOnPage.x;
926+
constrainedPoint.x = this._pickupPositionOnPage.x;
918927
}
919928

920929
if (this._boundaryRect) {
@@ -926,11 +935,11 @@ export class DragRef<T = any> {
926935
const minX = boundaryRect.left + pickupX;
927936
const maxX = boundaryRect.right - (previewRect.width - pickupX);
928937

929-
point.x = clamp(point.x, minX, maxX);
930-
point.y = clamp(point.y, minY, maxY);
938+
constrainedPoint.x = clamp(constrainedPoint.x, minX, maxX);
939+
constrainedPoint.y = clamp(constrainedPoint.y, minY, maxY);
931940
}
932941

933-
return point;
942+
return constrainedPoint;
934943
}
935944

936945

@@ -984,7 +993,7 @@ export class DragRef<T = any> {
984993
}
985994

986995
/** Point on the page or within an element. */
987-
interface Point {
996+
export interface Point {
988997
x: number;
989998
y: number;
990999
}

tools/public_api_guard/cdk/drag-drop.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export declare class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDes
1212
_placeholderTemplate: CdkDragPlaceholder;
1313
_previewTemplate: CdkDragPreview;
1414
boundaryElementSelector: string;
15+
constrainPosition?: (point: Point) => Point;
1516
data: T;
1617
disabled: boolean;
1718
dragStartDelay: number;
@@ -204,6 +205,7 @@ export declare class DragDropRegistry<I, C extends {
204205

205206
export declare class DragRef<T = any> {
206207
beforeStarted: Subject<void>;
208+
constrainPosition?: (point: Point) => Point;
207209
data: T;
208210
disabled: boolean;
209211
dragStartDelay: number;

0 commit comments

Comments
 (0)