Skip to content

feat(drag-drop): add option to match size of dragged element in custom preview #18362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/cdk/drag-drop/directives/drag-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import {Directive, TemplateRef, Input} from '@angular/core';
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';

/**
* Element that will be used as a template for the preview
Expand All @@ -18,5 +19,14 @@ import {Directive, TemplateRef, Input} from '@angular/core';
export class CdkDragPreview<T = any> {
/** Context data to be added to the preview template instance. */
@Input() data: T;

/** Whether the preview should preserve the same size as the item that is being dragged. */
@Input()
get matchSize(): boolean { return this._matchSize; }
set matchSize(value: boolean) { this._matchSize = coerceBooleanProperty(value); }
private _matchSize = false;

constructor(public templateRef: TemplateRef<T>) {}

static ngAcceptInputType_matchSize: BooleanInput;
}
41 changes: 37 additions & 4 deletions src/cdk/drag-drop/directives/drag.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2890,6 +2890,37 @@ describe('CdkDrag', () => {
expect(preview.classList).toContain('custom-class');
}));

it('should be able to apply the size of the dragged element to a custom preview',
fakeAsync(() => {
const fixture = createComponent(DraggableInDropZoneWithCustomPreview);
fixture.componentInstance.matchPreviewSize = true;
fixture.detectChanges();
const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement;
const itemRect = item.getBoundingClientRect();

startDraggingViaMouse(fixture, item);

const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement;

expect(preview).toBeTruthy();
expect(preview.style.width).toBe(`${itemRect.width}px`);
expect(preview.style.height).toBe(`${itemRect.height}px`);
}));

it('should preserve the pickup position if the custom preview inherits the size of the ' +
'dragged element', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZoneWithCustomPreview);
fixture.componentInstance.matchPreviewSize = true;
fixture.detectChanges();
const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement;

startDraggingViaMouse(fixture, item, 50, 50);

const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement;

expect(preview.style.transform).toBe('translate3d(8px, 33px, 0px)');
}));

it('should not throw when custom preview only has text', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZoneWithCustomTextOnlyPreview);
fixture.detectChanges();
Expand Down Expand Up @@ -5009,10 +5040,11 @@ class DraggableInScrollableHorizontalDropZone extends DraggableInHorizontalDropZ
{{item}}

<ng-container *ngIf="renderCustomPreview">
<div
class="custom-preview"
style="width: 50px; height: 50px; background: purple;"
*cdkDragPreview>Custom preview</div>
<ng-template cdkDragPreview [matchSize]="matchPreviewSize">
<div
class="custom-preview"
style="width: 50px; height: 50px; background: purple;">Custom preview</div>
</ng-template>
</ng-container>
</div>
</div>
Expand All @@ -5024,6 +5056,7 @@ class DraggableInDropZoneWithCustomPreview {
items = ['Zero', 'One', 'Two', 'Three'];
boundarySelector: string;
renderCustomPreview = true;
matchPreviewSize = false;
previewClass: string | string[];
constrainPosition: (point: Point) => Point;
}
Expand Down
1 change: 1 addition & 0 deletions src/cdk/drag-drop/directives/drag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
const preview = this._previewTemplate ? {
template: this._previewTemplate.templateRef,
context: this._previewTemplate.data,
matchSize: this._previewTemplate.matchSize,
viewContainer: this._viewContainerRef
} : null;

Expand Down
4 changes: 4 additions & 0 deletions src/cdk/drag-drop/drag-drop.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ restrict the user to only be able to do so using a handle element, you can do it
When a `cdkDrag` element is picked up, it will create a preview element visible while dragging.
By default, this will be a clone of the original element positioned next to the user's cursor.
This preview can be customized, though, by providing a custom template via `*cdkDragPreview`.
Using the default configuration the custom preview won't match the size of the original dragged
element, because the CDK doesn't make assumptions about the element's content. If you want the
size to be matched, you can pass `true` to the `matchSize` input.

Note that the cloned element will remove its `id` attribute in order to avoid having multiple
elements with the same `id` on the page. This will cause any CSS that targets that `id` not
to be applied.
Expand Down
47 changes: 34 additions & 13 deletions src/cdk/drag-drop/drag-ref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ interface DragHelperTemplate<T = any> {
context: T;
}

/** Template that can be used to create a drag preview element. */
interface DragPreviewTemplate<T = any> extends DragHelperTemplate<T> {
matchSize?: boolean;
}

/** Point on the page or within an element. */
export interface Point {
x: number;
Expand Down Expand Up @@ -189,7 +194,7 @@ export class DragRef<T = any> {
private _boundaryRect?: ClientRect;

/** Element that will be used as a template to create the draggable item's preview. */
private _previewTemplate?: DragHelperTemplate | null;
private _previewTemplate?: DragPreviewTemplate | null;

/** Template for placeholder element rendered to show where a draggable would be dropped. */
private _placeholderTemplate?: DragHelperTemplate | null;
Expand Down Expand Up @@ -321,7 +326,7 @@ export class DragRef<T = any> {
* Registers the template that should be used for the drag preview.
* @param template Template that from which to stamp out the preview.
*/
withPreviewTemplate(template: DragHelperTemplate | null): this {
withPreviewTemplate(template: DragPreviewTemplate | null): this {
this._previewTemplate = template;
return this;
}
Expand Down Expand Up @@ -758,10 +763,12 @@ export class DragRef<T = any> {
this._boundaryRect = this._boundaryElement.getBoundingClientRect();
}

// If we have a custom preview template, the element won't be visible anyway so we avoid the
// extra `getBoundingClientRect` calls and just move the preview next to the cursor.
this._pickupPositionInElement = this._previewTemplate && this._previewTemplate.template ?
{x: 0, y: 0} :
// If we have a custom preview we can't know ahead of time how large it'll be so we position
// it next to the cursor. The exception is when the consumer has opted into making the preview
// the same size as the root element, in which case we do know the size.
const previewTemplate = this._previewTemplate;
this._pickupPositionInElement = previewTemplate && previewTemplate.template &&
!previewTemplate.matchSize ? {x: 0, y: 0} :
this._getPointerPositionInElement(referenceElement, event);
const pointerPosition = this._pickupPositionOnPage = this._getPointerPositionOnPage(event);
this._pointerDirectionDelta = {x: 0, y: 0};
Expand Down Expand Up @@ -861,16 +868,17 @@ export class DragRef<T = any> {
previewConfig!.context);
preview = getRootNode(viewRef, this._document);
this._previewRef = viewRef;
preview.style.transform =
getTransform(this._pickupPositionOnPage.x, this._pickupPositionOnPage.y);

if (previewConfig!.matchSize) {
matchElementSize(preview, this._rootElement);
} else {
preview.style.transform =
getTransform(this._pickupPositionOnPage.x, this._pickupPositionOnPage.y);
}
} else {
const element = this._rootElement;
const elementRect = element.getBoundingClientRect();

preview = deepCloneNode(element);
preview.style.width = `${elementRect.width}px`;
preview.style.height = `${elementRect.height}px`;
preview.style.transform = getTransform(elementRect.left, elementRect.top);
matchElementSize(preview, element);
}

extendStyles(preview.style, {
Expand Down Expand Up @@ -1279,3 +1287,16 @@ function getRootNode(viewRef: EmbeddedViewRef<any>, _document: Document): HTMLEl

return rootNode as HTMLElement;
}

/**
* Matches the target element's size to the source's size.
* @param target Element that needs to be resized.
* @param source Element whose size needs to be matched.
*/
function matchElementSize(target: HTMLElement, source: HTMLElement): void {
const sourceRect = source.getBoundingClientRect();

target.style.width = `${sourceRect.width}px`;
target.style.height = `${sourceRect.height}px`;
target.style.transform = getTransform(sourceRect.left, sourceRect.top);
}
7 changes: 5 additions & 2 deletions tools/public_api_guard/cdk/drag-drop.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,12 @@ export declare class CdkDragPlaceholder<T = any> {

export declare class CdkDragPreview<T = any> {
data: T;
get matchSize(): boolean;
set matchSize(value: boolean);
templateRef: TemplateRef<T>;
constructor(templateRef: TemplateRef<T>);
static ɵdir: i0.ɵɵDirectiveDefWithMeta<CdkDragPreview<any>, "ng-template[cdkDragPreview]", never, { "data": "data"; }, {}, never>;
static ngAcceptInputType_matchSize: BooleanInput;
static ɵdir: i0.ɵɵDirectiveDefWithMeta<CdkDragPreview<any>, "ng-template[cdkDragPreview]", never, { "data": "data"; "matchSize": "matchSize"; }, {}, never>;
static ɵfac: i0.ɵɵFactoryDef<CdkDragPreview<any>>;
}

Expand Down Expand Up @@ -305,7 +308,7 @@ export declare class DragRef<T = any> {
withDirection(direction: Direction): this;
withHandles(handles: (HTMLElement | ElementRef<HTMLElement>)[]): this;
withPlaceholderTemplate(template: DragHelperTemplate | null): this;
withPreviewTemplate(template: DragHelperTemplate | null): this;
withPreviewTemplate(template: DragPreviewTemplate | null): this;
withRootElement(rootElement: ElementRef<HTMLElement> | HTMLElement): this;
}

Expand Down