fix(chat): maintain bottom scroll on input resize

This commit is contained in:
Leonard Kim 2019-05-08 10:45:32 -07:00 committed by virtuacoplenny
parent dfe5fbb702
commit d86b60ea72
3 changed files with 74 additions and 2 deletions

View File

@ -47,6 +47,9 @@ class Chat extends AbstractChat<Props> {
// Bind event handlers so they are only bound once for every instance.
this._renderPanelContent = this._renderPanelContent.bind(this);
// Bind event handlers so they are only bound once for every instance.
this._onChatInputResize = this._onChatInputResize.bind(this);
}
/**
@ -87,6 +90,19 @@ class Chat extends AbstractChat<Props> {
);
}
_onChatInputResize: () => void;
/**
* Callback invoked when {@code ChatInput} changes height. Preserves
* displaying the latest message if it is scrolled to.
*
* @private
* @returns {void}
*/
_onChatInputResize() {
this._messageContainerRef.current.maybeUpdateBottomScroll();
}
/**
* Returns a React Element for showing chat messages and a form to send new
* chat messages.
@ -100,7 +116,7 @@ class Chat extends AbstractChat<Props> {
<MessageContainer
messages = { this.props._messages }
ref = { this._messageContainerRef } />
<ChatInput />
<ChatInput onResize = { this._onChatInputResize } />
</>
);
}

View File

@ -22,6 +22,12 @@ type Props = {
*/
dispatch: Dispatch<any>,
/**
* Optional callback to invoke when the chat textarea has auto-resized to
* fit overflowing text.
*/
onResize: ?Function,
/**
* Invoked to obtain translated strings.
*/
@ -120,6 +126,7 @@ class ChatInput extends Component<Props, State> {
inputRef = { this._setTextAreaRef }
maxRows = { 5 }
onChange = { this._onMessageChange }
onHeightChange = { this.props.onResize }
onKeyDown = { this._onDetectSubmit }
placeholder = { this.props.t('chat.messagebox') }
value = { this.state.message } />

View File

@ -13,12 +13,25 @@ import ChatMessageGroup from './ChatMessageGroup';
* @extends AbstractMessageContainer
*/
export default class MessageContainer extends AbstractMessageContainer {
/**
* Whether or not chat has been scrolled to the bottom of the screen. Used
* to determine if chat should be scrolled automatically to the bottom when
* the {@code ChatInput} resizes.
*/
_isScrolledToBottom: boolean;
/**
* Reference to the HTML element at the end of the list of displayed chat
* messages. Used for scrolling to the end of the chat messages.
*/
_messagesListEndRef: Object;
/**
* A React ref to the HTML element containing all {@code ChatMessageGroup}
* instances.
*/
_messageListRef: Object;
/**
* Initializes a new {@code MessageContainer} instance.
*
@ -28,7 +41,12 @@ export default class MessageContainer extends AbstractMessageContainer {
constructor(props: Props) {
super(props);
this._isScrolledToBottom = true;
this._messageListRef = React.createRef();
this._messagesListEndRef = React.createRef();
this._onChatScroll = this._onChatScroll.bind(this);
}
/**
@ -50,13 +68,29 @@ export default class MessageContainer extends AbstractMessageContainer {
});
return (
<div id = 'chatconversation'>
<div
id = 'chatconversation'
onScroll = { this._onChatScroll }
ref = { this._messageListRef }>
{ messages }
<div ref = { this._messagesListEndRef } />
</div>
);
}
/**
* Scrolls to the bottom again if the instance had previously been scrolled
* to the bottom. This method is used when a resize has occurred below the
* instance and bottom scroll needs to be maintained.
*
* @returns {void}
*/
maybeUpdateBottomScroll() {
if (this._isScrolledToBottom) {
this.scrollToBottom(false);
}
}
/**
* Automatically scrolls the displayed chat messages down to the latest.
*
@ -71,4 +105,19 @@ export default class MessageContainer extends AbstractMessageContainer {
}
_getMessagesGroupedBySender: () => Array<Array<Object>>;
_onChatScroll: () => void;
/**
* Callback invoked to listen to the current scroll location.
*
* @private
* @returns {void}
*/
_onChatScroll() {
const element = this._messageListRef.current;
this._isScrolledToBottom
= element.scrollHeight - element.scrollTop === element.clientHeight;
}
}