diff --git a/mobile/capacitor.config.json b/mobile/capacitor.config.json index afd4b3d..cb11352 100644 --- a/mobile/capacitor.config.json +++ b/mobile/capacitor.config.json @@ -17,7 +17,7 @@ "offsetForKeyboardInsetBug": false }, "Keyboard": { - "resize": "none" + "resize": "native" }, "SplashScreen": { "launchShowDuration": 900, diff --git a/server/public/home.html b/server/public/home.html index c8e3a62..13efa92 100644 --- a/server/public/home.html +++ b/server/public/home.html @@ -1112,7 +1112,7 @@ - // juggling here — we only tag so CSS can make small native-only tweaks if needed. (function(){ try{ var C=window.Capacitor; if(!C||!C.isNativePlatform||!C.isNativePlatform()) return; var p=(C.getPlatform&&C.getPlatform())||''; var r=document.documentElement; r.classList.add('native-app'); if(p) r.classList.add('native-'+p); - // Keyboard — DEVICE-INDEPENDENT & mode-adaptive (works whether the WebView is resize:native or - // resize:none). We lift the composer so it sits on the keyboard: - // • resize:none → the WebView doesn't move, so lift by the FULL keyboard height (reported at - // keyboardWillShow, before any animation) → the CSS transition slides it up WITH the keyboard. - // • resize:native→ the WebView already shrank by the keyboard height, so lift only the RESIDUAL - // (clientHeight − visualViewport.height). Detected once by checking if clientHeight shrank. - // No per-device constants — the keyboard height comes from the plugin / VisualViewport. - function setKb(px){ px=Math.max(0,Math.round(px)); r.style.setProperty('--kb',px+'px'); var b=document.getElementById('msgs'); if(b) b.scrollTop=b.scrollHeight; } - var kbMode=null; var KB=C.Plugins&&C.Plugins.Keyboard; - if(KB && KB.addListener){ - KB.addListener('keyboardWillShow', function(info){ - document.body.classList.add('kb-open'); - var raw=(info&&info.keyboardHeight)||0; var kbFull = raw>window.innerHeight ? raw/(window.devicePixelRatio||1) : raw; // normalize px→pt - var baseH=document.documentElement.clientHeight; - if(kbMode==='none') setKb(kbFull); // known non-resizing WebView → lift immediately (smooth) - setTimeout(function(){ - var nowH=document.documentElement.clientHeight; - if(!kbMode) kbMode=(baseH-nowH>20)?'native':'none'; - if(kbMode==='native'){ var vv=window.visualViewport; setKb(vv?Math.max(0, nowH - vv.height - vv.offsetTop):0); } - else setKb(kbFull); - }, 60); - }); - KB.addListener('keyboardWillHide', function(){ r.style.setProperty('--kb','0px'); document.body.classList.remove('kb-open'); }); + // Keyboard — DEVICE-INDEPENDENT. The exact amount to lift the composer so it sits on the keyboard is + // the difference between the layout viewport and the VISIBLE viewport: clientHeight - visualViewport. + // This is self-adapting: with resize:native the WebView already shrank so the difference is the small + // prediction-bar residual; with resize:none the difference is the full keyboard. We read it LIVE from + // VisualViewport (never on a timer, which would sample a mid-animation, inconsistent state — that was + // the bug that left a gap). See bzKeyboardLift() in the unit test for the pure logic. + if(window.visualViewport){ + var vv=window.visualViewport; + var applyKb=function(){ + var kb=Math.max(0, Math.round(document.documentElement.clientHeight - vv.height - vv.offsetTop)); + r.style.setProperty('--kb', kb+'px'); + document.body.classList.toggle('kb-open', kb>4); + var b=document.getElementById('msgs'); if(b) b.scrollTop=b.scrollHeight; + }; + vv.addEventListener('resize', applyKb); vv.addEventListener('scroll', applyKb); } - // Live-track while the keyboard is up (covers rotation + resize:native's late resize) without doubling. - if(window.visualViewport){ window.visualViewport.addEventListener('resize', function(){ if(kbMode==='native' && document.body.classList.contains('kb-open')){ var vv=window.visualViewport; setKb(Math.max(0, document.documentElement.clientHeight - vv.height - vv.offsetTop)); } }); } }catch(_){} })();
Biz Connect
Loading…
@@ -1902,7 +1892,7 @@ function convoShellHTML(it){ + '' + '' + '' - + '' + + '' + '' + '' + '' diff --git a/test/keyboard-lift.test.js b/test/keyboard-lift.test.js new file mode 100644 index 0000000..dc0d6db --- /dev/null +++ b/test/keyboard-lift.test.js @@ -0,0 +1,31 @@ +// Unit test for the mobile keyboard-lift logic (pure function, no DOM). +// The composer must sit exactly on top of the keyboard. The amount to lift it is the gap between the +// LAYOUT viewport (document.documentElement.clientHeight) and the VISIBLE viewport +// (window.visualViewport.height + offsetTop). This is self-adapting across Capacitor keyboard resize +// modes, and needs no per-device constant. +// +// Run: node test/keyboard-lift.test.js + +function bzKeyboardLift(clientHeight, vvHeight, vvTop) { + return Math.max(0, Math.round(clientHeight - vvHeight - (vvTop || 0))); +} + +// Cases use REAL numbers captured from the on-device debug readout (iPhone, dpr=3): +const cases = [ + // [name, clientHeight, vvHeight, vvTop, expectedLift] + ['keyboard closed (all equal) ', 926, 926, 0, 0], + ['resize:native open (WebView shrank; residual)', 581, 535, 0, 46], // <- the device values you sent + ['resize:none open (WebView full; full lift) ', 926, 581, 0, 345], + ['with a visual-viewport offset ', 926, 560, 20, 346], + ['guard: never negative ', 500, 600, 0, 0], +]; + +let pass = 0, fail = 0; +for (const [name, ch, vh, vt, expected] of cases) { + const got = bzKeyboardLift(ch, vh, vt); + const ok = got === expected; + console.log(`${ok ? 'PASS' : 'FAIL'} ${name} lift=${got}${ok ? '' : ` (expected ${expected})`}`); + ok ? pass++ : fail++; +} +console.log(`\n${pass} passed, ${fail} failed`); +process.exit(fail ? 1 : 0);