From 8906294092f2292ca3dbc01b6e056d932f4910ab Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 1 Mar 2022 13:17:33 +0100 Subject: [PATCH] fix(cdk/drag-drop): error if preview dimensions are accessed too early Fixes a null pointer exception that can happen if the page is scrolled before the user has scrolled the minimum distance. The problem was that we were expecting for the dimensions to be defined by the time the user has had the chance to scroll, but in some cases that can be circumvented. These changes move the measurement to a getter method so that they're guaranteed to be available when they are requested. Fixes #24497. --- src/cdk/drag-drop/drag-ref.ts | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/cdk/drag-drop/drag-ref.ts b/src/cdk/drag-drop/drag-ref.ts index 2101a3c21c22..bf9cf9e8b6cc 100644 --- a/src/cdk/drag-drop/drag-ref.ts +++ b/src/cdk/drag-drop/drag-ref.ts @@ -240,7 +240,7 @@ export class DragRef { /** Whether the native dragging interactions have been enabled on the root element. */ private _nativeInteractionsEnabled = true; - /** Cached dimensions of the preview element. */ + /** Cached dimensions of the preview element. Should be read via `_getPreviewRect`. */ private _previewRect?: ClientRect; /** Cached dimensions of the boundary element. */ @@ -686,15 +686,6 @@ export class DragRef { return; } - // We only need the preview dimensions if we have a boundary element. - if (this._boundaryElement) { - // Cache the preview element rect if we haven't cached it already or if - // we cached it too early before the element dimensions were computed. - if (!this._previewRect || (!this._previewRect.width && !this._previewRect.height)) { - this._previewRect = (this._preview || this._rootElement).getBoundingClientRect(); - } - } - // We prevent the default action down here so that we know that dragging has started. This is // important for touch devices where doing this too early can unnecessarily block scrolling, // if there's a dragging delay. @@ -1246,11 +1237,11 @@ export class DragRef { if (this._boundaryRect) { const {x: pickupX, y: pickupY} = this._pickupPositionInElement; const boundaryRect = this._boundaryRect; - const previewRect = this._previewRect!; + const {width: previewWidth, height: previewHeight} = this._getPreviewRect(); const minY = boundaryRect.top + pickupY; - const maxY = boundaryRect.bottom - (previewRect.height - pickupY); + const maxY = boundaryRect.bottom - (previewHeight - pickupY); const minX = boundaryRect.left + pickupX; - const maxX = boundaryRect.right - (previewRect.width - pickupX); + const maxX = boundaryRect.right - (previewWidth - pickupX); x = clamp(x, minX, maxX); y = clamp(y, minY, maxY); @@ -1518,6 +1509,17 @@ export class DragRef { return coerceElement(previewContainer); } + + /** Lazily resolves and returns the dimensions of the preview. */ + private _getPreviewRect(): ClientRect { + // Cache the preview element rect if we haven't cached it already or if + // we cached it too early before the element dimensions were computed. + if (!this._previewRect || (!this._previewRect.width && !this._previewRect.height)) { + this._previewRect = (this._preview || this._rootElement).getBoundingClientRect(); + } + + return this._previewRect; + } } /**