chat: stick-to-bottom auto-scroll with jump-to-latest button
Nightly Build / build (push) Successful in 6m51s
Nightly Build / build (push) Successful in 6m51s
Auto-scroll now yields when the reader scrolls away from the bottom, so a fast-streaming reply no longer fights someone reading the start. Stickiness is a flag driven by a passive scroll listener (not a per-flush distance check, which breaks when one flush adds more than the threshold of content); scrolling back within the band re-arms it. Centralised in chat-session.js (_scrollToBottom(force) + _forceScrollToBottom + _messagesContainer hook), removing the duplicated overrides in copilot.js / chat-page.js. Force-scroll is used where the latest must be shown: history load, chat reopen, approval/clarification prompts, and message send. A sticky 'jump to latest' affordance appears only while scrolled up.
This commit is contained in:
@@ -162,11 +162,8 @@ export class AppCopilot extends I18nMixin(ChatSession) {
|
||||
return this.querySelector('.copilot-textarea');
|
||||
}
|
||||
|
||||
_scrollToBottom() {
|
||||
this.updateComplete.then(() => {
|
||||
const el = this.querySelector('.copilot-messages');
|
||||
if (el) el.scrollTop = el.scrollHeight;
|
||||
});
|
||||
_messagesContainer() {
|
||||
return this.querySelector('.copilot-messages');
|
||||
}
|
||||
|
||||
_onMessagePushed(item) {
|
||||
@@ -389,6 +386,15 @@ export class AppCopilot extends I18nMixin(ChatSession) {
|
||||
${t('chat.thinking')}
|
||||
</div>
|
||||
` : nothing}
|
||||
|
||||
${this._showJump ? html`
|
||||
<button class="copilot-jump-btn" type="button"
|
||||
title=${t('chat.scroll_to_latest')}
|
||||
aria-label=${t('chat.scroll_to_latest')}
|
||||
@click=${() => this._jumpToBottom()}>
|
||||
<i class="bi bi-arrow-down-circle-fill"></i>
|
||||
</button>
|
||||
` : nothing}
|
||||
</div>
|
||||
|
||||
<div class="copilot-input-area">
|
||||
|
||||
@@ -43,7 +43,7 @@ export class ChatPage extends ChatSession {
|
||||
|
||||
updated(changed) {
|
||||
if (changed.has('visible') && this.visible) {
|
||||
this._scrollToBottom();
|
||||
this._forceScrollToBottom();
|
||||
}
|
||||
// The owner (mobile-app) re-points this chat by changing `source`. Switch the
|
||||
// live connection — base `_switchSource` tears down the WS, reloads that
|
||||
@@ -72,11 +72,8 @@ export class ChatPage extends ChatSession {
|
||||
return this.querySelector('.chat-page-textarea');
|
||||
}
|
||||
|
||||
_scrollToBottom() {
|
||||
this.updateComplete.then(() => {
|
||||
const el = this.querySelector('.chat-page-messages');
|
||||
if (el) el.scrollTop = el.scrollHeight;
|
||||
});
|
||||
_messagesContainer() {
|
||||
return this.querySelector('.chat-page-messages');
|
||||
}
|
||||
|
||||
_onMessagePushed(item) {
|
||||
@@ -184,6 +181,15 @@ export class ChatPage extends ChatSession {
|
||||
${t('chat.thinking')}
|
||||
</div>
|
||||
` : nothing}
|
||||
|
||||
${this._showJump ? html`
|
||||
<button class="copilot-jump-btn" type="button"
|
||||
title=${t('chat.scroll_to_latest')}
|
||||
aria-label=${t('chat.scroll_to_latest')}
|
||||
@click=${() => this._jumpToBottom()}>
|
||||
<i class="bi bi-arrow-down-circle-fill"></i>
|
||||
</button>
|
||||
` : nothing}
|
||||
</div>
|
||||
|
||||
<div class="chat-page-input-area">
|
||||
|
||||
Reference in New Issue
Block a user