feat(drag-drop): add the ability to customize how the position is constrained (#15137)

This commit is contained in:
Kristiyan Kostadinov
2019-03-06 18:58:54 +01:00
committed by Joey Perrott
parent 9d60a5da4c
commit 4704653cad
4 changed files with 77 additions and 8 deletions
+50 -1
View File
@@ -30,7 +30,7 @@ import {CdkDropList} from './drop-list';
import {CdkDragHandle} from './drag-handle';
import {CdkDropListGroup} from './drop-list-group';
import {extendStyles} from '../drag-styling';
import {DragRefConfig} from '../drag-ref';
import {DragRefConfig, Point} from '../drag-ref';
const ITEM_HEIGHT = 25;
const ITEM_WIDTH = 75;
@@ -697,6 +697,24 @@ describe('CdkDrag', () => {
expect(dragElement.style.transform).toBe('translate3d(100px, 100px, 0px)');
}));
it('should allow for the position constrain logic to be customized', fakeAsync(() => {
const fixture = createComponent(StandaloneDraggable);
const spy = jasmine.createSpy('constrain position spy').and.returnValue({
x: 50,
y: 50
} as Point);
fixture.componentInstance.constrainPosition = spy;
fixture.detectChanges();
const dragElement = fixture.componentInstance.dragElement.nativeElement;
expect(dragElement.style.transform).toBeFalsy();
dragElementViaMouse(fixture, dragElement, 300, 300);
expect(spy).toHaveBeenCalledWith(jasmine.objectContaining({x: 300, y: 300}));
expect(dragElement.style.transform).toBe('translate3d(50px, 50px, 0px)');
}));
it('should throw if attached to an ng-container', fakeAsync(() => {
expect(() => {
createComponent(DraggableOnNgContainer).detectChanges();
@@ -2105,6 +2123,33 @@ describe('CdkDrag', () => {
expect(Math.floor(previewRect.right)).toBe(Math.floor(listRect.right));
}));
it('should be able to constrain the preview position with a custom function', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZoneWithCustomPreview);
const spy = jasmine.createSpy('constrain position spy').and.returnValue({
x: 50,
y: 50
} as Point);
fixture.componentInstance.constrainPosition = spy;
fixture.detectChanges();
const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement;
startDraggingViaMouse(fixture, item);
const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement;
startDraggingViaMouse(fixture, item, 200, 200);
flush();
dispatchMouseEvent(document, 'mousemove', 200, 200);
fixture.detectChanges();
const previewRect = preview.getBoundingClientRect();
expect(spy).toHaveBeenCalledWith(jasmine.objectContaining({x: 200, y: 200}));
expect(Math.floor(previewRect.top)).toBe(50);
expect(Math.floor(previewRect.left)).toBe(50);
}));
it('should revert the element back to its parent after dragging with a custom ' +
'preview has stopped', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZoneWithCustomPreview);
@@ -3049,6 +3094,7 @@ describe('CdkDrag', () => {
cdkDrag
[cdkDragBoundary]="boundarySelector"
[cdkDragStartDelay]="dragStartDelay"
[cdkDragConstrainPosition]="constrainPosition"
(cdkDragStarted)="startedSpy($event)"
(cdkDragReleased)="releasedSpy($event)"
(cdkDragEnded)="endedSpy($event)"
@@ -3065,6 +3111,7 @@ class StandaloneDraggable {
releasedSpy = jasmine.createSpy('released spy');
boundarySelector: string;
dragStartDelay: number;
constrainPosition: (point: Point) => Point;
}
@Component({
@@ -3263,6 +3310,7 @@ class DraggableInHorizontalDropZone {
<div
*ngFor="let item of items"
cdkDrag
[cdkDragConstrainPosition]="constrainPosition"
[cdkDragBoundary]="boundarySelector"
style="width: 100%; height: ${ITEM_HEIGHT}px; background: red;">
{{item}}
@@ -3283,6 +3331,7 @@ class DraggableInDropZoneWithCustomPreview {
items = ['Zero', 'One', 'Two', 'Three'];
boundarySelector: string;
renderCustomPreview = true;
constrainPosition: (point: Point) => Point;
}
+10 -1
View File
@@ -48,7 +48,7 @@ import {CdkDragPlaceholder} from './drag-placeholder';
import {CdkDragPreview} from './drag-preview';
import {CDK_DROP_LIST} from '../drop-list-container';
import {CDK_DRAG_PARENT} from '../drag-parent';
import {DragRef, DragRefConfig} from '../drag-ref';
import {DragRef, DragRefConfig, Point} from '../drag-ref';
import {DropListRef} from '../drop-list-ref';
import {CdkDropListInternal as CdkDropList} from './drop-list';
import {DragDrop} from '../drag-drop';
@@ -127,6 +127,14 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
}
private _disabled = false;
/**
* Function that can be used to customize the logic of how the position of the drag item
* is limited while it's being dragged. Gets called with a point containing the current position
* of the user's pointer on the page and should return a point describing where the item should
* be rendered.
*/
@Input('cdkDragConstrainPosition') constrainPosition?: (point: Point) => Point;
/** Emits when the user starts dragging the item. */
@Output('cdkDragStarted') started: EventEmitter<CdkDragStart> = new EventEmitter<CdkDragStart>();
@@ -310,6 +318,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
ref.disabled = this.disabled;
ref.lockAxis = this.lockAxis;
ref.dragStartDelay = this.dragStartDelay;
ref.constrainPosition = this.constrainPosition;
ref
.withBoundaryElement(this._getBoundaryElement())
.withPlaceholderTemplate(placeholder)
+15 -6
View File
@@ -273,6 +273,14 @@ export class DragRef<T = any> {
/** Arbitrary data that can be attached to the drag item. */
data: T;
/**
* Function that can be used to customize the logic of how the position of the drag item
* is limited while it's being dragged. Gets called with a point containing the current position
* of the user's pointer on the page and should return a point describing where the item should
* be rendered.
*/
constrainPosition?: (point: Point) => Point;
constructor(
element: ElementRef<HTMLElement> | HTMLElement,
private _config: DragRefConfig,
@@ -909,12 +917,13 @@ export class DragRef<T = any> {
/** Gets the pointer position on the page, accounting for any position constraints. */
private _getConstrainedPointerPosition(event: MouseEvent | TouchEvent): Point {
const point = this._getPointerPositionOnPage(event);
const constrainedPoint = this.constrainPosition ? this.constrainPosition(point) : point;
const dropContainerLock = this._dropContainer ? this._dropContainer.lockAxis : null;
if (this.lockAxis === 'x' || dropContainerLock === 'x') {
point.y = this._pickupPositionOnPage.y;
constrainedPoint.y = this._pickupPositionOnPage.y;
} else if (this.lockAxis === 'y' || dropContainerLock === 'y') {
point.x = this._pickupPositionOnPage.x;
constrainedPoint.x = this._pickupPositionOnPage.x;
}
if (this._boundaryRect) {
@@ -926,11 +935,11 @@ export class DragRef<T = any> {
const minX = boundaryRect.left + pickupX;
const maxX = boundaryRect.right - (previewRect.width - pickupX);
point.x = clamp(point.x, minX, maxX);
point.y = clamp(point.y, minY, maxY);
constrainedPoint.x = clamp(constrainedPoint.x, minX, maxX);
constrainedPoint.y = clamp(constrainedPoint.y, minY, maxY);
}
return point;
return constrainedPoint;
}
@@ -984,7 +993,7 @@ export class DragRef<T = any> {
}
/** Point on the page or within an element. */
interface Point {
export interface Point {
x: number;
y: number;
}
+2
View File
@@ -12,6 +12,7 @@ export declare class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDes
_placeholderTemplate: CdkDragPlaceholder;
_previewTemplate: CdkDragPreview;
boundaryElementSelector: string;
constrainPosition?: (point: Point) => Point;
data: T;
disabled: boolean;
dragStartDelay: number;
@@ -204,6 +205,7 @@ export declare class DragDropRegistry<I, C extends {
export declare class DragRef<T = any> {
beforeStarted: Subject<void>;
constrainPosition?: (point: Point) => Point;
data: T;
disabled: boolean;
dragStartDelay: number;