Fix bug with scroll buttons visibility when no scroller in chat panel

This commit is contained in:
dimitrievgs 2026-05-16 20:41:16 +03:00
parent 9aefdaaa69
commit cdf45430aa
2 changed files with 85 additions and 1 deletions

View File

@ -63,6 +63,12 @@ export class ChatPanel {
private currentReferences: FileReference[] = []; private currentReferences: FileReference[] = [];
private isProcessingReference = false; private isProcessingReference = false;
// Свойства для обсерверов прокрутки
private resizeObserver: ResizeObserver | null = null;
private scrollDebounceTimer: number | null = null; // Таймер для debounce
private cachedScrollContainer: HTMLElement | null = null;
private cachedBtnWrapper: HTMLElement | null = null;
constructor(container: HTMLElement, props: ChatPanelProps) { constructor(container: HTMLElement, props: ChatPanelProps) {
this.container = container; this.container = container;
this.props = props; this.props = props;
@ -429,6 +435,11 @@ export class ChatPanel {
} }
destroy(): void { destroy(): void {
if (this.scrollDebounceTimer !== null) {
window.clearTimeout(this.scrollDebounceTimer);
}
if (this.resizeObserver) this.resizeObserver.disconnect();
if (this.referenceAutocomplete) { if (this.referenceAutocomplete) {
this.referenceAutocomplete.destroy(); this.referenceAutocomplete.destroy();
} }
@ -652,6 +663,8 @@ export class ChatPanel {
// Обработчики Drag-n-Drop для внешних файлов // Обработчики Drag-n-Drop для внешних файлов
this.setupDragAndDrop(); this.setupDragAndDrop();
this.setupScrollObservers();
} }
// ----------------------------------------------- Reference Processing --------------------------------------------------------------- // ----------------------------------------------- Reference Processing ---------------------------------------------------------------
@ -1360,4 +1373,64 @@ export class ChatPanel {
new Notice(`Прикреплено файлов: ${addedCount}`, 2000); new Notice(`Прикреплено файлов: ${addedCount}`, 2000);
} }
} }
// ----------------------------------------------- Scroll Observers ---------------------------------------------------------------
/**
* Настраивает наблюдатели за размерами окна и внутренним контентом чата
*/
private setupScrollObservers(): void {
const historyContainer = this.container.querySelector('#chat-history') as HTMLElement;
this.cachedScrollContainer = this.container.closest('.view-content') as HTMLElement;
this.cachedBtnWrapper = this.container.querySelector('.sticky-btn-wrapper') as HTMLElement;
if (!this.cachedScrollContainer || !this.cachedBtnWrapper) return;
// Оставляем только ResizeObserver.
this.resizeObserver = new ResizeObserver(() => {
this.scheduleDebouncedScrollUpdate();
});
// Следим за родителем (ресайз панелей)
this.resizeObserver.observe(this.cachedScrollContainer);
// Следим за контентом (чтобы поймать появление overlay-скроллбаров на Mac)
if (historyContainer) {
this.resizeObserver.observe(historyContainer);
}
// Проверяем состояние при инициализации
setTimeout(() => this.updateScrollButtonsVisibility(), 0);
}
/**
* Схлопывает частые вызовы событий (debounce)
*/
private scheduleDebouncedScrollUpdate(): void {
if (this.scrollDebounceTimer !== null) {
window.clearTimeout(this.scrollDebounceTimer);
}
// Запускаем перерасчет только если изменения "затихли" на 200мс
this.scrollDebounceTimer = window.setTimeout(() => {
this.updateScrollButtonsVisibility();
this.scrollDebounceTimer = null;
}, 200);
}
/**
* Проверяет наличие скроллбара на родительском элементе и скрывает/показывает кнопки
*/
private updateScrollButtonsVisibility(): void {
if (!this.cachedScrollContainer || !this.cachedBtnWrapper) return;
// DOM больше не сканируется через closest/querySelector, элементы закэшированы
const hasScrollbar = this.cachedScrollContainer.scrollHeight > (this.cachedScrollContainer.clientHeight + 2);
if (hasScrollbar) {
this.cachedBtnWrapper.classList.add('is-visible');
} else {
this.cachedBtnWrapper.classList.remove('is-visible');
}
}
} }

View File

@ -419,7 +419,7 @@
} }
.stop-button, .submit-button { .stop-button, .submit-button {
padding: 4px 8px; /* Восстанавливаем паддинг */ padding: 4px 10px; /* Восстанавливаем паддинг */
border: none; /* Убираем рамку */ border: none; /* Убираем рамку */
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
@ -518,6 +518,17 @@
width: 100%; width: 100%;
pointer-events: none; pointer-events: none;
flex-shrink: 0; flex-shrink: 0;
/* Скрываем по умолчанию */
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}
/* Класс, который добавляется JS, когда есть скролл */
.sticky-btn-wrapper.is-visible {
opacity: 1;
visibility: visible;
} }
/* Добавить общий стиль для обеих кнопок и специфичный для новой */ /* Добавить общий стиль для обеих кнопок и специфичный для новой */