Skip to content

Commit 8a71288

Browse files
authored
fix(drag-drop): handle custom preview/placeholder with multiple root nodes (#18829)
We support passing in a custom preview or placeholder using an `ng-template`, but we were only taking the first `rootNode` from the template.
1 parent c71bbf2 commit 8a71288

File tree

2 files changed

+77
-6
lines changed

2 files changed

+77
-6
lines changed

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2995,6 +2995,21 @@ describe('CdkDrag', () => {
29952995
expect(preview.textContent!.trim()).toContain('Hello One');
29962996
}));
29972997

2998+
it('should handle custom preview with multiple root nodes', fakeAsync(() => {
2999+
const fixture = createComponent(DraggableInDropZoneWithCustomMultiNodePreview);
3000+
fixture.detectChanges();
3001+
const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement;
3002+
3003+
expect(() => {
3004+
startDraggingViaMouse(fixture, item);
3005+
}).not.toThrow();
3006+
3007+
const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement;
3008+
3009+
expect(preview).toBeTruthy();
3010+
expect(preview.textContent!.trim()).toContain('HelloOne');
3011+
}));
3012+
29983013
it('should be able to customize the placeholder', fakeAsync(() => {
29993014
const fixture = createComponent(DraggableInDropZoneWithCustomPlaceholder);
30003015
fixture.detectChanges();
@@ -3067,6 +3082,21 @@ describe('CdkDrag', () => {
30673082
expect(placeholder.textContent!.trim()).toContain('Hello One');
30683083
}));
30693084

3085+
it('should handle custom placeholder with multiple root nodes', fakeAsync(() => {
3086+
const fixture = createComponent(DraggableInDropZoneWithCustomMultiNodePlaceholder);
3087+
fixture.detectChanges();
3088+
const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement;
3089+
3090+
expect(() => {
3091+
startDraggingViaMouse(fixture, item);
3092+
}).not.toThrow();
3093+
3094+
const placeholder = document.querySelector('.cdk-drag-placeholder')! as HTMLElement;
3095+
3096+
expect(placeholder).toBeTruthy();
3097+
expect(placeholder.textContent!.trim()).toContain('HelloOne');
3098+
}));
3099+
30703100
it('should clear the `transform` value from siblings when item is dropped`', fakeAsync(() => {
30713101
const fixture = createComponent(DraggableInDropZone);
30723102
fixture.detectChanges();
@@ -5209,6 +5239,28 @@ class DraggableInDropZoneWithCustomTextOnlyPreview {
52095239
}
52105240

52115241

5242+
@Component({
5243+
template: `
5244+
<div cdkDropList style="width: 100px; background: pink;">
5245+
<div
5246+
*ngFor="let item of items"
5247+
cdkDrag
5248+
style="width: 100%; height: ${ITEM_HEIGHT}px; background: red;">
5249+
{{item}}
5250+
<ng-template cdkDragPreview>
5251+
<span>Hello</span>
5252+
<span>{{item}}</span>
5253+
</ng-template>
5254+
</div>
5255+
</div>
5256+
`
5257+
})
5258+
class DraggableInDropZoneWithCustomMultiNodePreview {
5259+
@ViewChild(CdkDropList) dropInstance: CdkDropList;
5260+
@ViewChildren(CdkDrag) dragItems: QueryList<CdkDrag>;
5261+
items = ['Zero', 'One', 'Two', 'Three'];
5262+
}
5263+
52125264
@Component({
52135265
template: `
52145266
<div
@@ -5257,6 +5309,25 @@ class DraggableInDropZoneWithCustomTextOnlyPlaceholder {
52575309
items = ['Zero', 'One', 'Two', 'Three'];
52585310
}
52595311

5312+
@Component({
5313+
template: `
5314+
<div cdkDropList style="width: 100px; background: pink;">
5315+
<div *ngFor="let item of items" cdkDrag
5316+
style="width: 100%; height: ${ITEM_HEIGHT}px; background: red;">
5317+
{{item}}
5318+
<ng-template cdkDragPlaceholder>
5319+
<span>Hello</span>
5320+
<span>{{item}}</span>
5321+
</ng-template>
5322+
</div>
5323+
</div>
5324+
`
5325+
})
5326+
class DraggableInDropZoneWithCustomMultiNodePlaceholder {
5327+
@ViewChildren(CdkDrag) dragItems: QueryList<CdkDrag>;
5328+
items = ['Zero', 'One', 'Two', 'Three'];
5329+
}
5330+
52605331
const CONNECTED_DROP_ZONES_STYLES = [`
52615332
.cdk-drop-list {
52625333
display: block;

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,15 +1302,15 @@ function getPreviewInsertionPoint(documentRef: any): HTMLElement {
13021302
* If the root is not an HTML element it gets wrapped in one.
13031303
*/
13041304
function getRootNode(viewRef: EmbeddedViewRef<any>, _document: Document): HTMLElement {
1305-
const rootNode: Node = viewRef.rootNodes[0];
1305+
const rootNodes: Node[] = viewRef.rootNodes;
13061306

1307-
if (rootNode.nodeType !== _document.ELEMENT_NODE) {
1308-
const wrapper = _document.createElement('div');
1309-
wrapper.appendChild(rootNode);
1310-
return wrapper;
1307+
if (rootNodes.length === 1 && rootNodes[0].nodeType === _document.ELEMENT_NODE) {
1308+
return rootNodes[0] as HTMLElement;
13111309
}
13121310

1313-
return rootNode as HTMLElement;
1311+
const wrapper = _document.createElement('div');
1312+
rootNodes.forEach(node => wrapper.appendChild(node));
1313+
return wrapper;
13141314
}
13151315

13161316
/**

0 commit comments

Comments
 (0)