diff --git a/js/main.js b/js/main.js index 4b4b14c2..4ada64f6 100644 --- a/js/main.js +++ b/js/main.js @@ -1065,3 +1065,57 @@ document.fonts.addEventListener("loadingdone", (event) => { } }, 50); }); + +//------------------------------------------------ +// Preserve chat scroll position on textarea resize +//------------------------------------------------ +(function() { + let chatParent = null; + let initialState = null; + let debounceTimeout = null; + + function getChatParent() { + if (!chatParent) chatParent = document.querySelector(".chat-parent"); + return chatParent; + } + + function getTextarea() { + return document.querySelector("#chat-input textarea"); + } + + document.addEventListener("input", function(e) { + if (e.target.matches("#chat-input textarea")) { + const chat = getChatParent(); + const textarea = getTextarea(); + + if (chat && textarea) { + // Capture initial state only on first input of a typing sequence + if (!initialState) { + initialState = { + scrollTop: chat.scrollTop, + textareaHeight: textarea.offsetHeight + }; + } + + // Clear existing timeout + clearTimeout(debounceTimeout); + + // Wait for typing to stop (50ms delay) + debounceTimeout = setTimeout(() => { + const finalTextareaHeight = textarea.offsetHeight; + const totalGrowth = finalTextareaHeight - initialState.textareaHeight; + const targetScroll = initialState.scrollTop + totalGrowth; + + const restore = () => { chat.scrollTop = targetScroll; }; + + restore(); + requestAnimationFrame(restore); + setTimeout(restore, 0); + setTimeout(restore, 10); + + initialState = null; + }, 50); + } + } + }, true); +})();