Skip to content

Commit

Permalink
feat(drag-drop): allow for custom class to be set on preview (#17304)
Browse files Browse the repository at this point in the history
When we create a preview for a dragged element, we put it in the `body` in order to avoid issues with `z-index` and `overflow`, however this makes it harder to style the element. When the previews were implemented initially, the thinking was the users could customize it either by providing a custom preview, in which case they can set as many classes as they want, or by targeting something like `.some-element.cdk-drag-preview` if they're using the default one. The problem with the latter is that it doesn't allow for something like a theme to be propagated to the preview.

These changes add an extra input that allows users to set a class on the preview element for easier customization.

Fixes #17089.
  • Loading branch information
crisbeto authored and mmalerba committed Oct 18, 2019
1 parent f10b222 commit 34e848f
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 2 deletions.
42 changes: 42 additions & 0 deletions src/cdk/drag-drop/directives/drag.spec.ts
Expand Up @@ -1977,6 +1977,31 @@ describe('CdkDrag', () => {
.toBeFalsy('Expected preview to be removed from the DOM if the transition timed out');
}));

it('should be able to set a single class on a preview', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZone);
fixture.componentInstance.previewClass = 'custom-class';
fixture.detectChanges();
const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement;

startDraggingViaMouse(fixture, item);

const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement;
expect(preview.classList).toContain('custom-class');
}));

it('should be able to set multiple classes on a preview', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZone);
fixture.componentInstance.previewClass = ['custom-class-1', 'custom-class-2'];
fixture.detectChanges();
const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement;

startDraggingViaMouse(fixture, item);

const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement;
expect(preview.classList).toContain('custom-class-1');
expect(preview.classList).toContain('custom-class-2');
}));

it('should emit the released event as soon as the item is released', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZone);
fixture.detectChanges();
Expand Down Expand Up @@ -2759,6 +2784,19 @@ describe('CdkDrag', () => {
expect(preview.style.transform).toBe('translate3d(100px, 50px, 0px)');
}));

it('should be able to set a class on a custom preview', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZoneWithCustomPreview);
fixture.componentInstance.previewClass = 'custom-class';
fixture.detectChanges();
const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement;

startDraggingViaMouse(fixture, item);

const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement;
expect(preview.classList).toContain('custom-preview');
expect(preview.classList).toContain('custom-class');
}));

it('should not throw when custom preview only has text', fakeAsync(() => {
const fixture = createComponent(DraggableInDropZoneWithCustomTextOnlyPreview);
fixture.detectChanges();
Expand Down Expand Up @@ -4467,6 +4505,7 @@ const DROP_ZONE_FIXTURE_TEMPLATE = `
cdkDrag
[cdkDragData]="item"
[cdkDragBoundary]="boundarySelector"
[cdkDragPreviewClass]="previewClass"
[style.height.px]="item.height"
[style.margin-bottom.px]="item.margin"
style="width: 100%; background: red;">{{item.value}}</div>
Expand All @@ -4485,6 +4524,7 @@ class DraggableInDropZone {
];
dropZoneId = 'items';
boundarySelector: string;
previewClass: string | string[];
sortedSpy = jasmine.createSpy('sorted spy');
droppedSpy = jasmine.createSpy('dropped spy').and.callFake((event: CdkDragDrop<string[]>) => {
moveItemInArray(this.items, event.previousIndex, event.currentIndex);
Expand Down Expand Up @@ -4604,6 +4644,7 @@ class DraggableInScrollableHorizontalDropZone extends DraggableInHorizontalDropZ
cdkDrag
[cdkDragConstrainPosition]="constrainPosition"
[cdkDragBoundary]="boundarySelector"
[cdkDragPreviewClass]="previewClass"
style="width: 100%; height: ${ITEM_HEIGHT}px; background: red;">
{{item}}
Expand All @@ -4623,6 +4664,7 @@ class DraggableInDropZoneWithCustomPreview {
items = ['Zero', 'One', 'Two', 'Three'];
boundarySelector: string;
renderCustomPreview = true;
previewClass: string | string[];
constrainPosition: (point: Point) => Point;
}

Expand Down
4 changes: 4 additions & 0 deletions src/cdk/drag-drop/directives/drag.ts
Expand Up @@ -145,6 +145,9 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
*/
@Input('cdkDragConstrainPosition') constrainPosition?: (point: Point, dragRef: DragRef) => Point;

/** Class to be added to the preview element. */
@Input('cdkDragPreviewClass') previewClass: string | string[];

/** Emits when the user starts dragging the item. */
@Output('cdkDragStarted') started: EventEmitter<CdkDragStart> = new EventEmitter<CdkDragStart>();

Expand Down Expand Up @@ -343,6 +346,7 @@ export class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDestroy {
ref.dragStartDelay = (typeof dragStartDelay === 'object' && dragStartDelay) ?
dragStartDelay : coerceNumberProperty(dragStartDelay);
ref.constrainPosition = this.constrainPosition;
ref.previewClass = this.previewClass;
ref
.withBoundaryElement(this._getBoundaryElement())
.withPlaceholderTemplate(placeholder)
Expand Down
15 changes: 13 additions & 2 deletions src/cdk/drag-drop/drag-ref.ts
Expand Up @@ -209,6 +209,9 @@ export class DragRef<T = any> {
*/
dragStartDelay: number | {touch: number, mouse: number} = 0;

/** Class to be added to the preview element. */
previewClass: string|string[]|undefined;

/** Whether starting to drag this element is disabled. */
get disabled(): boolean {
return this._disabled || !!(this._dropContainer && this._dropContainer.disabled);
Expand Down Expand Up @@ -844,6 +847,7 @@ export class DragRef<T = any> {
*/
private _createPreviewElement(): HTMLElement {
const previewConfig = this._previewTemplate;
const previewClass = this.previewClass;
const previewTemplate = previewConfig ? previewConfig.template : null;
let preview: HTMLElement;

Expand All @@ -868,7 +872,7 @@ export class DragRef<T = any> {
// It's important that we disable the pointer events on the preview, because
// it can throw off the `document.elementFromPoint` calls in the `CdkDropList`.
pointerEvents: 'none',
// We have to reset the margin, because can throw off positioning relative to the viewport.
// We have to reset the margin, because it can throw off positioning relative to the viewport.
margin: '0',
position: 'fixed',
top: '0',
Expand All @@ -877,10 +881,17 @@ export class DragRef<T = any> {
});

toggleNativeDragInteractions(preview, false);

preview.classList.add('cdk-drag-preview');
preview.setAttribute('dir', this._direction);

if (previewClass) {
if (Array.isArray(previewClass)) {
previewClass.forEach(className => preview.classList.add(className));
} else {
preview.classList.add(previewClass);
}
}

return preview;
}

Expand Down
2 changes: 2 additions & 0 deletions tools/public_api_guard/cdk/drag-drop.d.ts
Expand Up @@ -29,6 +29,7 @@ export declare class CdkDrag<T = any> implements AfterViewInit, OnChanges, OnDes
};
lockAxis: 'x' | 'y';
moved: Observable<CdkDragMove<T>>;
previewClass: string | string[];
released: EventEmitter<CdkDragRelease>;
rootElementSelector: string;
started: EventEmitter<CdkDragStart>;
Expand Down Expand Up @@ -237,6 +238,7 @@ export declare class DragRef<T = any> {
y: -1 | 0 | 1;
};
}>;
previewClass: string | string[] | undefined;
released: Subject<{
source: DragRef<any>;
}>;
Expand Down

1 comment on commit 34e848f

@Rlcolli4
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has this been implemented? I am trying to use this in my code without any success

Please sign in to comment.