fix(chat): maintain bottom scroll on input resize
This commit is contained in:
parent
dfe5fbb702
commit
d86b60ea72
|
@ -47,6 +47,9 @@ class Chat extends AbstractChat<Props> {
|
||||||
|
|
||||||
// Bind event handlers so they are only bound once for every instance.
|
// Bind event handlers so they are only bound once for every instance.
|
||||||
this._renderPanelContent = this._renderPanelContent.bind(this);
|
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
|
* Returns a React Element for showing chat messages and a form to send new
|
||||||
* chat messages.
|
* chat messages.
|
||||||
|
@ -100,7 +116,7 @@ class Chat extends AbstractChat<Props> {
|
||||||
<MessageContainer
|
<MessageContainer
|
||||||
messages = { this.props._messages }
|
messages = { this.props._messages }
|
||||||
ref = { this._messageContainerRef } />
|
ref = { this._messageContainerRef } />
|
||||||
<ChatInput />
|
<ChatInput onResize = { this._onChatInputResize } />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,12 @@ type Props = {
|
||||||
*/
|
*/
|
||||||
dispatch: Dispatch<any>,
|
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.
|
* Invoked to obtain translated strings.
|
||||||
*/
|
*/
|
||||||
|
@ -120,6 +126,7 @@ class ChatInput extends Component<Props, State> {
|
||||||
inputRef = { this._setTextAreaRef }
|
inputRef = { this._setTextAreaRef }
|
||||||
maxRows = { 5 }
|
maxRows = { 5 }
|
||||||
onChange = { this._onMessageChange }
|
onChange = { this._onMessageChange }
|
||||||
|
onHeightChange = { this.props.onResize }
|
||||||
onKeyDown = { this._onDetectSubmit }
|
onKeyDown = { this._onDetectSubmit }
|
||||||
placeholder = { this.props.t('chat.messagebox') }
|
placeholder = { this.props.t('chat.messagebox') }
|
||||||
value = { this.state.message } />
|
value = { this.state.message } />
|
||||||
|
|
|
@ -13,12 +13,25 @@ import ChatMessageGroup from './ChatMessageGroup';
|
||||||
* @extends AbstractMessageContainer
|
* @extends AbstractMessageContainer
|
||||||
*/
|
*/
|
||||||
export default class MessageContainer 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
|
* 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.
|
* messages. Used for scrolling to the end of the chat messages.
|
||||||
*/
|
*/
|
||||||
_messagesListEndRef: Object;
|
_messagesListEndRef: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A React ref to the HTML element containing all {@code ChatMessageGroup}
|
||||||
|
* instances.
|
||||||
|
*/
|
||||||
|
_messageListRef: Object;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a new {@code MessageContainer} instance.
|
* Initializes a new {@code MessageContainer} instance.
|
||||||
*
|
*
|
||||||
|
@ -28,7 +41,12 @@ export default class MessageContainer extends AbstractMessageContainer {
|
||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this._isScrolledToBottom = true;
|
||||||
|
|
||||||
|
this._messageListRef = React.createRef();
|
||||||
this._messagesListEndRef = React.createRef();
|
this._messagesListEndRef = React.createRef();
|
||||||
|
|
||||||
|
this._onChatScroll = this._onChatScroll.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,13 +68,29 @@ export default class MessageContainer extends AbstractMessageContainer {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id = 'chatconversation'>
|
<div
|
||||||
|
id = 'chatconversation'
|
||||||
|
onScroll = { this._onChatScroll }
|
||||||
|
ref = { this._messageListRef }>
|
||||||
{ messages }
|
{ messages }
|
||||||
<div ref = { this._messagesListEndRef } />
|
<div ref = { this._messagesListEndRef } />
|
||||||
</div>
|
</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.
|
* Automatically scrolls the displayed chat messages down to the latest.
|
||||||
*
|
*
|
||||||
|
@ -71,4 +105,19 @@ export default class MessageContainer extends AbstractMessageContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
_getMessagesGroupedBySender: () => Array<Array<Object>>;
|
_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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue