From cd7d8c732998979011305952cc5b68fe198fc31f Mon Sep 17 00:00:00 2001 From: Teigen Date: Wed, 17 Jun 2026 17:43:02 +0800 Subject: [PATCH] fix(mobile): split CJK keyboard positioning by device size MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phones use translateY(-keyboardOffset) — CSS bottom is relative to layout viewport and keyboardOffset reliably lifts it above the keyboard (iOS doesn't auto-scroll the visual viewport for the CJK textarea on phones). iPad uses direct bottom positioning from keyboard height — translateY broke because iOS auto-scrolls the visual viewport when the CJK textarea receives focus, making keyboardOffset approach 0. --- src/web/public/mobile-handlers.js | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/web/public/mobile-handlers.js b/src/web/public/mobile-handlers.js index be88a5a4..6ce78f9b 100644 --- a/src/web/public/mobile-handlers.js +++ b/src/web/public/mobile-handlers.js @@ -326,15 +326,26 @@ const KeyboardHandler = { } } - // CJK textarea: position on ALL touch devices (it's always position:fixed). - // Uses keyboard HEIGHT directly — independent of visualViewport.offsetTop - // which iOS adjusts when the CJK textarea receives focus/dictation. + // CJK textarea positioning (always position:fixed on touch devices). if (cjkInput?.classList.contains('cjk-input-visible') && keyboardHeight > 0) { - // On phones, sit above toolbar+accessory (84px). On tablets in normal - // flow, the toolbar/accessory are in-flow so use a smaller offset. - const bottomOffset = isSmallMedium ? 84 : 44; - cjkInput.style.bottom = `${keyboardHeight + bottomOffset}px`; - cjkInput.style.transform = ''; + if (isSmallMedium) { + // Phones: use translateY like toolbar/accessory bar. The CSS bottom + // (84px) is relative to the layout viewport; translateY lifts it + // above the keyboard. This works on phones where iOS doesn't + // auto-scroll the visual viewport for the CJK textarea. + const layoutHeight = window.innerHeight; + const visualBottom = window.visualViewport.offsetTop + window.visualViewport.height; + const keyboardOffset = Math.max(0, layoutHeight - visualBottom); + cjkInput.style.transform = keyboardOffset > 0 ? `translateY(${-keyboardOffset}px)` : ''; + cjkInput.style.bottom = ''; + } else { + // iPad: use direct bottom positioning from keyboard height. + // translateY breaks on iPad because iOS auto-scrolls the visual + // viewport when the CJK textarea receives focus, making + // keyboardOffset unreliable (approaches 0). + cjkInput.style.bottom = `${keyboardHeight + 44}px`; + cjkInput.style.transform = ''; + } } } else { this.resetLayout();