diff --git a/static/boot.js b/static/boot.js index ae17eb2e..ca94570a 100644 --- a/static/boot.js +++ b/static/boot.js @@ -1022,6 +1022,11 @@ let _imeComposing=false; })(); function _isImeEnter(e){return e.isComposing||e.keyCode===229||_imeComposing;} window._isImeEnter=_isImeEnter; +function _isVirtualKeyboardLikelyOpen(){ + const vv=window.visualViewport; + if(!vv||!window.innerHeight)return true; + return window.innerHeight-vv.height>120; +} $('msg').addEventListener('keydown',e=>{ // Autocomplete navigation when dropdown is open const dd=$('cmdDropdown'); @@ -1039,13 +1044,14 @@ $('msg').addEventListener('keydown',e=>{ } } // Send key: respect user preference. - // On touch-primary devices (software keyboard), default to Enter = newline - // since there's no physical Shift key. Users send via the Send button. + // On touch-primary devices with the software keyboard open, default to + // Enter = newline since there's no physical Shift key. Hardware keyboards on + // tablets keep desktop behavior when the viewport is not keyboard-shrunk. // The 'ctrl+enter' setting also uses this behavior (Enter = newline). // Users can override in Settings by explicitly choosing 'enter' mode. if(e.key==='Enter'){ if(_isImeEnter(e)){return;} - const _mobileDefault=matchMedia('(pointer:coarse)').matches&&window._sendKey==='enter'; + const _mobileDefault=matchMedia('(pointer:coarse)').matches&&window._sendKey==='enter'&&_isVirtualKeyboardLikelyOpen(); if(window._sendKey==='ctrl+enter'||_mobileDefault){ if(e.ctrlKey||e.metaKey){e.preventDefault();send();} } else { diff --git a/tests/test_mobile_layout.py b/tests/test_mobile_layout.py index 46ac1972..46cdc0a7 100644 --- a/tests/test_mobile_layout.py +++ b/tests/test_mobile_layout.py @@ -1188,6 +1188,24 @@ def test_mobile_enter_newline_uses_match_media(): "boot.js must use matchMedia('(pointer:coarse)') for mobile detection" +def test_mobile_enter_newline_checks_virtual_keyboard_viewport(): + """Touch devices should only force newline while the software keyboard is likely open.""" + boot_js = (REPO / "static" / "boot.js").read_text(encoding="utf-8") + assert "function _isVirtualKeyboardLikelyOpen()" in boot_js, \ + "boot.js must isolate the software-keyboard viewport heuristic" + assert "window.visualViewport" in boot_js and "window.innerHeight-vv.height>120" in boot_js, \ + "software-keyboard detection must compare visualViewport height against window.innerHeight" + assert "&&_isVirtualKeyboardLikelyOpen()" in boot_js, \ + "mobile Enter newline override must not apply when a hardware keyboard leaves the viewport unshrunk" + + +def test_mobile_enter_newline_preserves_legacy_fallback_without_visual_viewport(): + """Browsers without visualViewport should keep the previous touch Enter=newline behavior.""" + boot_js = (REPO / "static" / "boot.js").read_text(encoding="utf-8") + assert "if(!vv||!window.innerHeight)return true;" in boot_js, \ + "missing visualViewport support must preserve the legacy touch-primary newline fallback" + + def test_mobile_enter_newline_only_overrides_enter_default(): """Mobile newline override must only apply when _sendKey is the default 'enter'.""" boot_js = (REPO / "static" / "boot.js").read_text(encoding="utf-8")