diff --git a/js/main.js b/js/main.js index 67f60279..337131c2 100644 --- a/js/main.js +++ b/js/main.js @@ -1105,3 +1105,64 @@ document.fonts.addEventListener("loadingdone", (event) => { // Initial call to set the margin based on current state updateMargin(); })(); + +//------------------------------------------------ +// Optimize typing in all textareas +//------------------------------------------------ + +(function() { + document.querySelectorAll("textarea").forEach(textarea => { + const computedStyle = getComputedStyle(textarea); + const MIN_HEIGHT = parseInt(computedStyle.minHeight) || textarea.offsetHeight || 42; + const configuredMax = parseInt(computedStyle.maxHeight) || 400; + + let rafId = null; + let isOurResize = false; + + function doResize() { + rafId = null; + isOurResize = true; + + // Recalculate max height each time + const maxHeight = Math.min(configuredMax, window.innerHeight * 0.5); + + textarea.style.height = "auto"; + const contentHeight = textarea.scrollHeight; + const clampedHeight = Math.min(maxHeight, Math.max(MIN_HEIGHT, contentHeight)); + + textarea.style.height = clampedHeight + "px"; + textarea.style.overflowY = contentHeight > maxHeight ? "auto" : "hidden"; + + isOurResize = false; + } + + function scheduleResize() { + if (rafId === null) { + rafId = requestAnimationFrame(doResize); + } + } + + const desc = Object.getOwnPropertyDescriptor(CSSStyleDeclaration.prototype, "height"); + const originalSet = desc?.set || function(v) { this.setProperty("height", v); }; + const originalGet = desc?.get || function() { return this.getPropertyValue("height"); }; + + Object.defineProperty(textarea.style, "height", { + get() { return originalGet.call(this); }, + set(value) { + if (isOurResize) originalSet.call(this, value); + else scheduleResize(); + }, + configurable: true + }); + + textarea.addEventListener("input", scheduleResize, { passive: true }); + doResize(); + }); + + // Trigger resize on all textareas when window resizes + window.addEventListener("resize", () => { + document.querySelectorAll("textarea").forEach(ta => { + ta.dispatchEvent(new Event("input")); + }); + }, { passive: true }); +})();