Skip to content

Commit ebab924

Browse files
committed
refactor(cdk/drag-drop): move preview-related logic into a separate class
Moves the logic for creating and managing the preview into a separate class to make it a bit easier to manage. This new class is internal only.
1 parent 94a0834 commit ebab924

File tree

4 files changed

+226
-128
lines changed

4 files changed

+226
-128
lines changed

src/cdk/drag-drop/dom/root-node.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {EmbeddedViewRef} from '@angular/core';
10+
11+
/**
12+
* Gets the root HTML element of an embedded view.
13+
* If the root is not an HTML element it gets wrapped in one.
14+
*/
15+
export function getRootNode(viewRef: EmbeddedViewRef<any>, _document: Document): HTMLElement {
16+
const rootNodes: Node[] = viewRef.rootNodes;
17+
18+
if (rootNodes.length === 1 && rootNodes[0].nodeType === _document.ELEMENT_NODE) {
19+
return rootNodes[0] as HTMLElement;
20+
}
21+
22+
const wrapper = _document.createElement('div');
23+
rootNodes.forEach(node => wrapper.appendChild(node));
24+
return wrapper;
25+
}

src/cdk/drag-drop/dom/styling.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,25 @@ export function combineTransforms(transform: string, initialTransform?: string):
9494
? transform + ' ' + initialTransform
9595
: transform;
9696
}
97+
98+
/**
99+
* Matches the target element's size to the source's size.
100+
* @param target Element that needs to be resized.
101+
* @param sourceRect Dimensions of the source element.
102+
*/
103+
export function matchElementSize(target: HTMLElement, sourceRect: DOMRect): void {
104+
target.style.width = `${sourceRect.width}px`;
105+
target.style.height = `${sourceRect.height}px`;
106+
target.style.transform = getTransform(sourceRect.left, sourceRect.top);
107+
}
108+
109+
/**
110+
* Gets a 3d `transform` that can be applied to an element.
111+
* @param x Desired position of the element along the X axis.
112+
* @param y Desired position of the element along the Y axis.
113+
*/
114+
export function getTransform(x: number, y: number): string {
115+
// Round the transforms since some browsers will
116+
// blur the elements for sub-pixel transforms.
117+
return `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`;
118+
}

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

Lines changed: 23 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ import {DragDropRegistry} from './drag-drop-registry';
2222
import {
2323
combineTransforms,
2424
DragCSSStyleDeclaration,
25-
extendStyles,
25+
getTransform,
2626
toggleNativeDragInteractions,
2727
toggleVisibility,
2828
} from './dom/styling';
29-
import {getTransformTransitionDurationInMs} from './dom/transition-duration';
3029
import {getMutableClientRect, adjustDomRect} from './dom/dom-rect';
3130
import {ParentPositionTracker} from './dom/parent-position-tracker';
3231
import {deepCloneNode} from './dom/clone-node';
32+
import {DragPreviewTemplate, PreviewRef} from './preview-ref';
33+
import {getRootNode} from './dom/root-node';
3334

3435
/** Object that can be used to configure the behavior of DragRef. */
3536
export interface DragRefConfig {
@@ -82,11 +83,6 @@ interface DragHelperTemplate<T = any> {
8283
context: T;
8384
}
8485

85-
/** Template that can be used to create a drag preview element. */
86-
interface DragPreviewTemplate<T = any> extends DragHelperTemplate<T> {
87-
matchSize?: boolean;
88-
}
89-
9086
/** Point on the page or within an element. */
9187
export interface Point {
9288
x: number;
@@ -118,10 +114,7 @@ export type PreviewContainer = 'global' | 'parent' | ElementRef<HTMLElement> | H
118114
*/
119115
export class DragRef<T = any> {
120116
/** Element displayed next to the user's pointer while the element is dragged. */
121-
private _preview: HTMLElement;
122-
123-
/** Reference to the view of the preview element. */
124-
private _previewRef: EmbeddedViewRef<any> | null;
117+
private _preview: PreviewRef | null;
125118

126119
/** Container into which to insert the preview. */
127120
private _previewContainer: PreviewContainer | undefined;
@@ -627,9 +620,8 @@ export class DragRef<T = any> {
627620

628621
/** Destroys the preview element and its ViewRef. */
629622
private _destroyPreview() {
630-
this._preview?.remove();
631-
this._previewRef?.destroy();
632-
this._preview = this._previewRef = null!;
623+
this._preview?.destroy();
624+
this._preview = null;
633625
}
634626

635627
/** Destroys the placeholder element and its ViewRef. */
@@ -834,14 +826,24 @@ export class DragRef<T = any> {
834826

835827
// Create the preview after the initial transform has
836828
// been cached, because it can be affected by the transform.
837-
this._preview = this._createPreviewElement();
829+
this._preview = new PreviewRef(
830+
this._document,
831+
this._rootElement,
832+
this._direction,
833+
this._initialDomRect!,
834+
this._previewTemplate || null,
835+
this.previewClass || null,
836+
this._pickupPositionOnPage,
837+
this._initialTransform,
838+
this._config.zIndex || 1000,
839+
);
840+
this._preview.attach(this._getPreviewInsertionPoint(parent, shadowRoot));
838841

839842
// We move the element out at the end of the body and we make it hidden, because keeping it in
840843
// place will throw off the consumer's `:last-child` selectors. We can't remove the element
841844
// from the DOM completely, because iOS will stop firing all subsequent events in the chain.
842845
toggleVisibility(element, false, dragImportantProperties);
843846
this._document.body.appendChild(parent.replaceChild(placeholder, element));
844-
this._getPreviewInsertionPoint(parent, shadowRoot).appendChild(this._preview);
845847
this.started.next({source: this, event}); // Emit before notifying the container.
846848
dropContainer.start();
847849
this._initialContainer = dropContainer;
@@ -1056,75 +1058,6 @@ export class DragRef<T = any> {
10561058
}
10571059
}
10581060

1059-
/**
1060-
* Creates the element that will be rendered next to the user's pointer
1061-
* and will be used as a preview of the element that is being dragged.
1062-
*/
1063-
private _createPreviewElement(): HTMLElement {
1064-
const previewConfig = this._previewTemplate;
1065-
const previewClass = this.previewClass;
1066-
const previewTemplate = previewConfig ? previewConfig.template : null;
1067-
let preview: HTMLElement;
1068-
1069-
if (previewTemplate && previewConfig) {
1070-
// Measure the element before we've inserted the preview
1071-
// since the insertion could throw off the measurement.
1072-
const rootRect = previewConfig.matchSize ? this._initialDomRect : null;
1073-
const viewRef = previewConfig.viewContainer.createEmbeddedView(
1074-
previewTemplate,
1075-
previewConfig.context,
1076-
);
1077-
viewRef.detectChanges();
1078-
preview = getRootNode(viewRef, this._document);
1079-
this._previewRef = viewRef;
1080-
if (previewConfig.matchSize) {
1081-
matchElementSize(preview, rootRect!);
1082-
} else {
1083-
preview.style.transform = getTransform(
1084-
this._pickupPositionOnPage.x,
1085-
this._pickupPositionOnPage.y,
1086-
);
1087-
}
1088-
} else {
1089-
preview = deepCloneNode(this._rootElement);
1090-
matchElementSize(preview, this._initialDomRect!);
1091-
1092-
if (this._initialTransform) {
1093-
preview.style.transform = this._initialTransform;
1094-
}
1095-
}
1096-
1097-
extendStyles(
1098-
preview.style,
1099-
{
1100-
// It's important that we disable the pointer events on the preview, because
1101-
// it can throw off the `document.elementFromPoint` calls in the `CdkDropList`.
1102-
'pointer-events': 'none',
1103-
// We have to reset the margin, because it can throw off positioning relative to the viewport.
1104-
'margin': '0',
1105-
'position': 'fixed',
1106-
'top': '0',
1107-
'left': '0',
1108-
'z-index': `${this._config.zIndex || 1000}`,
1109-
},
1110-
dragImportantProperties,
1111-
);
1112-
1113-
toggleNativeDragInteractions(preview, false);
1114-
preview.classList.add('cdk-drag-preview');
1115-
preview.setAttribute('dir', this._direction);
1116-
1117-
if (previewClass) {
1118-
if (Array.isArray(previewClass)) {
1119-
previewClass.forEach(className => preview.classList.add(className));
1120-
} else {
1121-
preview.classList.add(previewClass);
1122-
}
1123-
}
1124-
1125-
return preview;
1126-
}
1127-
11281061
/**
11291062
* Animates the preview element from its current position to the location of the drop placeholder.
11301063
* @returns Promise that resolves when the animation completes.
@@ -1138,7 +1071,7 @@ export class DragRef<T = any> {
11381071
const placeholderRect = this._placeholder.getBoundingClientRect();
11391072

11401073
// Apply the class that adds a transition to the preview.
1141-
this._preview.classList.add('cdk-drag-animating');
1074+
this._preview!.addClass('cdk-drag-animating');
11421075

11431076
// Move the preview to the placeholder position.
11441077
this._applyPreviewTransform(placeholderRect.left, placeholderRect.top);
@@ -1147,7 +1080,7 @@ export class DragRef<T = any> {
11471080
// we need to trigger a style recalculation in order for the `cdk-drag-animating` class to
11481081
// apply its style, we take advantage of the available info to figure out whether we need to
11491082
// bind the event in the first place.
1150-
const duration = getTransformTransitionDurationInMs(this._preview);
1083+
const duration = this._preview!.getTransitionDuration();
11511084

11521085
if (duration === 0) {
11531086
return Promise.resolve();
@@ -1170,7 +1103,7 @@ export class DragRef<T = any> {
11701103
// Since we know how long it's supposed to take, add a timeout with a 50% buffer that'll
11711104
// fire if the transition hasn't completed when it was supposed to.
11721105
const timeout = setTimeout(handler as Function, duration * 1.5);
1173-
this._preview.addEventListener('transitionend', handler);
1106+
this._preview!.addEventListener('transitionend', handler);
11741107
});
11751108
});
11761109
}
@@ -1373,7 +1306,7 @@ export class DragRef<T = any> {
13731306
// it could be completely different and the transform might not make sense anymore.
13741307
const initialTransform = this._previewTemplate?.template ? undefined : this._initialTransform;
13751308
const transform = getTransform(x, y);
1376-
this._preview.style.transform = combineTransforms(transform, initialTransform);
1309+
this._preview!.setTransform(combineTransforms(transform, initialTransform));
13771310
}
13781311

13791312
/**
@@ -1559,7 +1492,7 @@ export class DragRef<T = any> {
15591492
// we cached it too early before the element dimensions were computed.
15601493
if (!this._previewRect || (!this._previewRect.width && !this._previewRect.height)) {
15611494
this._previewRect = this._preview
1562-
? this._preview.getBoundingClientRect()
1495+
? this._preview!.getBoundingClientRect()
15631496
: this._initialDomRect!;
15641497
}
15651498

@@ -1589,17 +1522,6 @@ export class DragRef<T = any> {
15891522
}
15901523
}
15911524

1592-
/**
1593-
* Gets a 3d `transform` that can be applied to an element.
1594-
* @param x Desired position of the element along the X axis.
1595-
* @param y Desired position of the element along the Y axis.
1596-
*/
1597-
function getTransform(x: number, y: number): string {
1598-
// Round the transforms since some browsers will
1599-
// blur the elements for sub-pixel transforms.
1600-
return `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`;
1601-
}
1602-
16031525
/** Clamps a value between a minimum and a maximum. */
16041526
function clamp(value: number, min: number, max: number) {
16051527
return Math.max(min, Math.min(max, value));
@@ -1613,33 +1535,6 @@ function isTouchEvent(event: MouseEvent | TouchEvent): event is TouchEvent {
16131535
return event.type[0] === 't';
16141536
}
16151537

1616-
/**
1617-
* Gets the root HTML element of an embedded view.
1618-
* If the root is not an HTML element it gets wrapped in one.
1619-
*/
1620-
function getRootNode(viewRef: EmbeddedViewRef<any>, _document: Document): HTMLElement {
1621-
const rootNodes: Node[] = viewRef.rootNodes;
1622-
1623-
if (rootNodes.length === 1 && rootNodes[0].nodeType === _document.ELEMENT_NODE) {
1624-
return rootNodes[0] as HTMLElement;
1625-
}
1626-
1627-
const wrapper = _document.createElement('div');
1628-
rootNodes.forEach(node => wrapper.appendChild(node));
1629-
return wrapper;
1630-
}
1631-
1632-
/**
1633-
* Matches the target element's size to the source's size.
1634-
* @param target Element that needs to be resized.
1635-
* @param sourceRect Dimensions of the source element.
1636-
*/
1637-
function matchElementSize(target: HTMLElement, sourceRect: DOMRect): void {
1638-
target.style.width = `${sourceRect.width}px`;
1639-
target.style.height = `${sourceRect.height}px`;
1640-
target.style.transform = getTransform(sourceRect.left, sourceRect.top);
1641-
}
1642-
16431538
/** Callback invoked for `selectstart` events inside the shadow DOM. */
16441539
function shadowDomSelectStart(event: Event) {
16451540
event.preventDefault();

0 commit comments

Comments
 (0)