-
Notifications
You must be signed in to change notification settings - Fork 608
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Chat: use resizeObserver to update scrollbar size and position #28111
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -1,12 +1,21 @@ | ||||||||
import domAdapter from '@js/core/dom_adapter'; | ||||||||
import type { dxElementWrapper } from '@js/core/renderer'; | ||||||||
import $ from '@js/core/renderer'; | ||||||||
import resizeObserverSingleton from '@js/core/resize_observer'; | ||||||||
import { contains } from '@js/core/utils/dom'; | ||||||||
import { hasWindow } from '@js/core/utils/window'; | ||||||||
import messageLocalization from '@js/localization/message'; | ||||||||
import { | ||||||||
isReachedBottom, | ||||||||
} from '@js/renovation/ui/scroll_view/utils/get_boundary_props'; | ||||||||
import { getScrollTopMax } from '@js/renovation/ui/scroll_view/utils/get_scroll_top_max'; | ||||||||
import type { Message } from '@js/ui/chat'; | ||||||||
import Scrollable from '@js/ui/scroll_view/ui.scrollable'; | ||||||||
import type { WidgetOptions } from '@js/ui/widget/ui.widget'; | ||||||||
import type { OptionChanged } from '@ts/core/widget/types'; | ||||||||
import Widget from '@ts/core/widget/widget'; | ||||||||
|
||||||||
import { isElementVisible } from '../splitter/utils/layout'; | ||||||||
import type { MessageGroupAlignment } from './messagegroup'; | ||||||||
import MessageGroup from './messagegroup'; | ||||||||
|
||||||||
|
@@ -23,7 +32,11 @@ export interface Properties extends WidgetOptions<MessageList> { | |||||||
} | ||||||||
|
||||||||
class MessageList extends Widget<Properties> { | ||||||||
_messageGroups?: MessageGroup[]; | ||||||||
private _messageGroups?: MessageGroup[]; | ||||||||
|
||||||||
private _containerClientHeight = 0; | ||||||||
|
||||||||
private _suppressResizeHandling?: boolean; | ||||||||
|
||||||||
private _scrollable!: Scrollable<unknown>; | ||||||||
|
||||||||
|
@@ -50,7 +63,47 @@ class MessageList extends Widget<Properties> { | |||||||
|
||||||||
this._renderMessageListContent(); | ||||||||
|
||||||||
this.update(); | ||||||||
this._attachResizeObserverSubscription(); | ||||||||
|
||||||||
this._suppressResizeHandling = true; | ||||||||
} | ||||||||
|
||||||||
_attachResizeObserverSubscription(): void { | ||||||||
if (hasWindow()) { | ||||||||
const element = this._getScrollContainer(); | ||||||||
|
||||||||
resizeObserverSingleton.unobserve(element); | ||||||||
resizeObserverSingleton.observe(element, (entry) => this._resizeHandler(entry)); | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
_isAttached(element: Element): boolean { | ||||||||
return !!contains(domAdapter.getBody(), element); | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [copypaste, shadowDOM] |
||||||||
} | ||||||||
|
||||||||
_resizeHandler({ contentRect, target }: ResizeObserverEntry): void { | ||||||||
const newHeight = contentRect.height; | ||||||||
|
||||||||
if (this._suppressResizeHandling | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [naming, ?excess cache?] Then maybe we can get rid of this private cache and just do it this way? const isAfterFirstRendering = this._containerClientHeight === 0; // or even undefined/null? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can simplified it this way:
|
||||||||
&& this._isAttached(target) | ||||||||
&& isElementVisible(target as HTMLElement) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [refactoring] |
||||||||
) { | ||||||||
this._scrollContentToLastMessage(); | ||||||||
|
||||||||
this._suppressResizeHandling = false; | ||||||||
} else { | ||||||||
const heightChange = this._containerClientHeight - newHeight; | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could u please add a Unit test for this scenario? Smth like "scrollable should keep scrolling position after container resize if scrolling position is not bottom" |
||||||||
|
||||||||
let { scrollTop } = target; | ||||||||
|
||||||||
if (heightChange >= 1 || !isReachedBottom(target as HTMLDivElement, target.scrollTop, 0, 1)) { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [excess code?] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
scrollTop += heightChange; | ||||||||
} | ||||||||
|
||||||||
this._scrollable.scrollTo({ top: scrollTop }); | ||||||||
} | ||||||||
|
||||||||
this._containerClientHeight = newHeight; | ||||||||
} | ||||||||
|
||||||||
_renderEmptyViewContent(): void { | ||||||||
|
@@ -162,23 +215,30 @@ class MessageList extends Widget<Properties> { | |||||||
|
||||||||
if (sender?.id === lastMessageGroupUserId) { | ||||||||
lastMessageGroup.renderMessage(message); | ||||||||
this.update(); | ||||||||
this._scrollContentToLastMessage(); | ||||||||
|
||||||||
return; | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
this._createMessageGroupComponent([message], sender?.id); | ||||||||
|
||||||||
this.update(); | ||||||||
this._scrollContentToLastMessage(); | ||||||||
} | ||||||||
|
||||||||
_$content(): dxElementWrapper { | ||||||||
return $(this._scrollable.content()); | ||||||||
} | ||||||||
|
||||||||
_scrollContentToLastMessage(): void { | ||||||||
this._scrollable.scrollTo({ top: this._$content().get(0).scrollHeight }); | ||||||||
const scrollOffsetTopMax = getScrollTopMax(this._getScrollContainer()); | ||||||||
|
||||||||
this._scrollable.scrollTo({ top: scrollOffsetTopMax }); | ||||||||
} | ||||||||
|
||||||||
_getScrollContainer(): HTMLElement { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [refactoring] |
||||||||
// @ts-expect-error | ||||||||
return $(this._scrollable.container()).get(0); | ||||||||
} | ||||||||
|
||||||||
_clean(): void { | ||||||||
|
@@ -238,13 +298,6 @@ class MessageList extends Widget<Properties> { | |||||||
super._optionChanged(args); | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
update(): void { | ||||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises | ||||||||
this._scrollable.update(); | ||||||||
|
||||||||
this._scrollContentToLastMessage(); | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
export default MessageList; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[SSR logic]
I believe it should be done in
_renderContent
, not_initMarkup
.Then u don't need
if (hasWindow()) {
inside of this method