Skip to content

Commit cd75979

Browse files
authored
fix(drag-drop): preview matchSize sometimes incorrect inside flex container (#19062)
We create a custom preview through `ViewContainerRef.createEmbeddedView` and we move it to the correct place in the DOM. The problem is that for the brief period that it's in the DOM, we also measure the host node if the `matchSize` option is set. In some cases (e.g. a flex container), this can throw off the size. These changes switch to measuring the size ahead of time when necessary. Fixes #19060.
1 parent 3c99efc commit cd75979

File tree

2 files changed

+62
-11
lines changed

2 files changed

+62
-11
lines changed

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3013,6 +3013,23 @@ describe('CdkDrag', () => {
30133013
expect(preview.style.transform).toBe('translate3d(8px, 33px, 0px)');
30143014
}));
30153015

3016+
it('should not have the size of the inserted preview affect the size applied via matchSize',
3017+
fakeAsync(() => {
3018+
const fixture = createComponent(DraggableInHorizontalFlexDropZoneWithMatchSizePreview);
3019+
fixture.detectChanges();
3020+
const item = fixture.componentInstance.dragItems.toArray()[1].element.nativeElement;
3021+
const itemRect = item.getBoundingClientRect();
3022+
3023+
startDraggingViaMouse(fixture, item);
3024+
fixture.detectChanges();
3025+
3026+
const preview = document.querySelector('.cdk-drag-preview')! as HTMLElement;
3027+
const previewRect = preview.getBoundingClientRect();
3028+
3029+
expect(Math.floor(previewRect.width)).toBe(Math.floor(itemRect.width));
3030+
expect(Math.floor(previewRect.height)).toBe(Math.floor(itemRect.height));
3031+
}));
3032+
30163033
it('should not throw when custom preview only has text', fakeAsync(() => {
30173034
const fixture = createComponent(DraggableInDropZoneWithCustomTextOnlyPreview);
30183035
fixture.detectChanges();
@@ -5792,6 +5809,40 @@ class PlainStandaloneDropList {
57925809
@ViewChild(CdkDropList) dropList: CdkDropList;
57935810
}
57945811

5812+
5813+
@Component({
5814+
styles: [`
5815+
.list {
5816+
display: flex;
5817+
width: 100px;
5818+
flex-direction: row;
5819+
}
5820+
5821+
.item {
5822+
display: flex;
5823+
flex-grow: 1;
5824+
flex-basis: 0;
5825+
min-height: 50px;
5826+
}
5827+
`],
5828+
template: `
5829+
<div class="list" cdkDropList cdkDropListOrientation="horizontal">
5830+
<div *ngFor="let item of items" class="item" cdkDrag>
5831+
{{item}}
5832+
5833+
<ng-template cdkDragPreview [matchSize]="true">
5834+
<div class="item">{{item}}</div>
5835+
</ng-template>
5836+
</div>
5837+
</div>
5838+
`
5839+
})
5840+
class DraggableInHorizontalFlexDropZoneWithMatchSizePreview {
5841+
@ViewChildren(CdkDrag) dragItems: QueryList<CdkDrag>;
5842+
items = ['Zero', 'One', 'Two'];
5843+
}
5844+
5845+
57955846
/**
57965847
* Drags an element to a position on the page using the mouse.
57975848
* @param fixture Fixture on which to run change detection.

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -887,23 +887,25 @@ export class DragRef<T = any> {
887887
const previewTemplate = previewConfig ? previewConfig.template : null;
888888
let preview: HTMLElement;
889889

890-
if (previewTemplate) {
891-
const viewRef = previewConfig!.viewContainer.createEmbeddedView(previewTemplate,
892-
previewConfig!.context);
890+
if (previewTemplate && previewConfig) {
891+
// Measure the element before we've inserted the preview
892+
// since the insertion could throw off the measurement.
893+
const rootRect = previewConfig.matchSize ? this._rootElement.getBoundingClientRect() : null;
894+
const viewRef = previewConfig.viewContainer.createEmbeddedView(previewTemplate,
895+
previewConfig.context);
893896
viewRef.detectChanges();
894897
preview = getRootNode(viewRef, this._document);
895898
this._previewRef = viewRef;
896-
897-
if (previewConfig!.matchSize) {
898-
matchElementSize(preview, this._rootElement);
899+
if (previewConfig.matchSize) {
900+
matchElementSize(preview, rootRect!);
899901
} else {
900902
preview.style.transform =
901903
getTransform(this._pickupPositionOnPage.x, this._pickupPositionOnPage.y);
902904
}
903905
} else {
904906
const element = this._rootElement;
905907
preview = deepCloneNode(element);
906-
matchElementSize(preview, element);
908+
matchElementSize(preview, element.getBoundingClientRect());
907909
}
908910

909911
extendStyles(preview.style, {
@@ -1332,11 +1334,9 @@ function getRootNode(viewRef: EmbeddedViewRef<any>, _document: Document): HTMLEl
13321334
/**
13331335
* Matches the target element's size to the source's size.
13341336
* @param target Element that needs to be resized.
1335-
* @param source Element whose size needs to be matched.
1337+
* @param sourceRect Dimensions of the source element.
13361338
*/
1337-
function matchElementSize(target: HTMLElement, source: HTMLElement): void {
1338-
const sourceRect = source.getBoundingClientRect();
1339-
1339+
function matchElementSize(target: HTMLElement, sourceRect: ClientRect): void {
13401340
target.style.width = `${sourceRect.width}px`;
13411341
target.style.height = `${sourceRect.height}px`;
13421342
target.style.transform = getTransform(sourceRect.left, sourceRect.top);

0 commit comments

Comments
 (0)