diff --git a/CHANGELOG.md b/CHANGELOG.md index 20215a771d..8014750865 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [4.15.1] - 2022-03-04 + +### Fixed + +- Fixes [#4196](https://github.com/microsoft/BotFramework-WebChat/issues/4196). Should render/mount to a detached DOM node without errors, by [@compulim](https://github.com/compulim), in PR [#4197](https://github.com/microsoft/BotFramework-WebChat/issues/4197) + ## [4.15.0] - 2022-03-03 ### Breaking changes diff --git a/__tests__/__image_snapshots__/html/simple-detached-js-should-render-on-a-detached-node-1-snap.png b/__tests__/__image_snapshots__/html/simple-detached-js-should-render-on-a-detached-node-1-snap.png new file mode 100644 index 0000000000..7384212f82 Binary files /dev/null and b/__tests__/__image_snapshots__/html/simple-detached-js-should-render-on-a-detached-node-1-snap.png differ diff --git a/__tests__/html/simple.detached.html b/__tests__/html/simple.detached.html new file mode 100644 index 0000000000..acb7b71a09 --- /dev/null +++ b/__tests__/html/simple.detached.html @@ -0,0 +1,42 @@ + + + + + + + + + + + + diff --git a/__tests__/html/simple.detached.js b/__tests__/html/simple.detached.js new file mode 100644 index 0000000000..c806d29ae9 --- /dev/null +++ b/__tests__/html/simple.detached.js @@ -0,0 +1,3 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +test('should render on a detached node', () => runHTML('simple.detached.html')); diff --git a/package-lock.json b/package-lock.json index 1e36e9030f..57f69ae77f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "botframework-webchat-root", - "version": "4.15.0", + "version": "4.15.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b0493151d2..d7e3638539 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "botframework-webchat-root", - "version": "4.15.0", + "version": "4.15.1", "private": true, "files": [ "lib/**/*" diff --git a/packages/component/src/BasicTranscript.tsx b/packages/component/src/BasicTranscript.tsx index 3bf014ed2b..a4f854b6b4 100644 --- a/packages/component/src/BasicTranscript.tsx +++ b/packages/component/src/BasicTranscript.tsx @@ -267,16 +267,22 @@ const InternalTranscript = forwardRef( if (scrollableElement && activityBoundingBoxElement) { // ESLint conflict with TypeScript. The result of getClientRects() is not an Array but DOMRectList, and cannot be destructured. // eslint-disable-next-line prefer-destructuring - const { height: activityHeight, y: activityY } = activityBoundingBoxElement.getClientRects()[0]; + const activityBoundingBoxElementClientRect = activityBoundingBoxElement.getClientRects()[0]; // ESLint conflict with TypeScript. The result of getClientRects() is not an Array but DOMRectList, and cannot be destructured. // eslint-disable-next-line prefer-destructuring - const { height: scrollableHeight } = scrollableElement.getClientRects()[0]; - const activityOffsetTop = activityY + scrollableElement.scrollTop; + const scrollableElementClientRect = scrollableElement.getClientRects()[0]; - const scrollTop = Math.min(activityOffsetTop, activityOffsetTop - scrollableHeight + activityHeight); + // If either the activity or the transcript scrollable is not on DOM, we will not scroll the view. + if (activityBoundingBoxElementClientRect && scrollableElementClientRect) { + const { height: activityHeight, y: activityY } = activityBoundingBoxElementClientRect; + const { height: scrollableHeight } = scrollableElementClientRect; + const activityOffsetTop = activityY + scrollableElement.scrollTop; - scrollToBottomScrollTo(scrollTop, { behavior }); + const scrollTop = Math.min(activityOffsetTop, activityOffsetTop - scrollableHeight + activityHeight); + + scrollToBottomScrollTo(scrollTop, { behavior }); + } } } }, @@ -342,7 +348,14 @@ const InternalTranscript = forwardRef( // "getClientRects()" is not returning an array, thus, it is not destructurable. // eslint-disable-next-line prefer-destructuring - const { bottom: scrollableClientBottom } = scrollableElement.getClientRects()[0]; + const scrollableElementClientRect = scrollableElement.getClientRects()[0]; + + // If the scrollable is not mounted, we cannot measure which activity is in view. Thus, we will not fire any events. + if (!scrollableElementClientRect) { + return; + } + + const { bottom: scrollableClientBottom } = scrollableElementClientRect; // Find the activity just above scroll view bottom. // If the scroll view is already on top, get the first activity. @@ -352,7 +365,14 @@ const InternalTranscript = forwardRef( ? activityElements .reverse() // Add subpixel tolerance - .find(([, element]) => element.getClientRects()[0].bottom < scrollableClientBottom + 1) + .find(([, element]) => { + // "getClientRects()" is not returning an array, thus, it is not destructurable. + // eslint-disable-next-line prefer-destructuring + const elementClientRect = element.getClientRects()[0]; + + // If the activity is not attached to DOM tree, we should not count it as "bottommost visible activity", as it is not visible. + return elementClientRect && elementClientRect.bottom < scrollableClientBottom + 1; + }) : activityElements[0] )?.[0];