fix(mobile): mode-adaptive smooth keyboard + force-latest-scroll + bottom-anchored file input
- Keyboard: auto-detect resize mode. resize:none -> lift full keyboard height at willShow so the CSS transition slides the composer up WITH the keyboard (smooth, no iOS resize lag). resize:native -> residual only. Config set to resize:none for the smooth path (needs build); web stays correct on the current resize:native build meanwhile. - #7: force scroll-to-bottom at 0/120/320/600ms on open so a chat always opens on the newest message. - File picker: anchor the file <input> to a fixed bottom spot (was display:none -> iOS dropped the menu mid-screen); now it comes up as a bottom sheet even as the composer moves. Build marker -> 2026-07-18-batch106. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,7 @@
|
||||
"offsetForKeyboardInsetBug": false
|
||||
},
|
||||
"Keyboard": {
|
||||
"resize": "native"
|
||||
"resize": "none"
|
||||
},
|
||||
"SplashScreen": {
|
||||
"launchShowDuration": 900,
|
||||
|
||||
+32
-16
@@ -1072,7 +1072,7 @@
|
||||
/* Composer clears the home indicator; with keyboard resize:none we raise it by --kb (the reported
|
||||
keyboard height, set by the Keyboard listener) so it sits on the keyboard. No home indicator while
|
||||
the keyboard is up, so --sab collapses to 0. */
|
||||
body.chat-open .content .panel{bottom:calc(var(--sab) + var(--kb));transition:bottom .18s ease;}
|
||||
body.chat-open .content .panel{bottom:calc(var(--sab) + var(--kb));transition:bottom .25s cubic-bezier(.17,.59,.4,1);}
|
||||
body.kb-open{--sab:0px;}
|
||||
/* Native-style push/pop: the conversation slides in from the right when opened and slides back out
|
||||
when you go back, so navigation has a clear direction (like Teams/WhatsApp). */
|
||||
@@ -1112,7 +1112,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<script src="/icons.js?v=6"></script>
|
||||
<script>window.__BUILD='2026-07-18-batch105';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
||||
<script>window.__BUILD='2026-07-18-batch106';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
||||
// Emoji are rendered with the OS's own (colour) emoji font — instant, zero network.
|
||||
//
|
||||
// We used to run Twemoji over every emoji, which swapped each one for an <img> pulled INDIVIDUALLY from
|
||||
@@ -1127,20 +1127,32 @@ function twemojify(_el){ /* native emoji: nothing to do */ }</script>
|
||||
// juggling here — we only tag <html> 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 & self-adjusting. resize:native already shrinks the WebView by the
|
||||
// keyboard height, so the composer only needs the RESIDUAL lift = (layout viewport height - visible
|
||||
// viewport height) — the sliver still under the keyboard's prediction/accessory bar. Read live from
|
||||
// the VisualViewport API so the composer follows the keyboard smoothly. No per-device constants.
|
||||
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);
|
||||
// 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'); });
|
||||
}
|
||||
// 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(_){} })();
|
||||
</script>
|
||||
<div class="loading" id="loading"><div class="ld-inner"><img src="/loaders/loader-orbit-dark.svg" alt="" width="72" height="72"><div class="ld-wm">Biz <b>Connect</b></div><span class="ld-sub">Loading…</span></div></div>
|
||||
@@ -1890,7 +1902,7 @@ function convoShellHTML(it){
|
||||
+ '<button type="submit" class="sendbtn" title="Send" aria-label="Send">'+ic('send',18)+'</button>'
|
||||
+ '</div>'
|
||||
+ '</div>'
|
||||
+ '<input type="file" id="fileInput" multiple style="display:none">'
|
||||
+ '<input type="file" id="fileInput" multiple style="position:fixed;left:50%;bottom:0;width:1px;height:1px;opacity:0;border:0;padding:0;pointer-events:none;">'
|
||||
+ '</form>'
|
||||
+ '<div class="emoji-pop" id="emojiPop" style="display:none"></div>'
|
||||
+ '<div class="mention-pop" id="mentionPop" style="display:none"></div>'
|
||||
@@ -2818,6 +2830,10 @@ async function openConvo(kind,id){
|
||||
if(el){ const sep=document.createElement('div'); sep.className='new-sep'; sep.innerHTML='<span>New messages</span>'; el.parentNode.insertBefore(sep, el); el.scrollIntoView({block:'start'}); el.parentNode.scrollTop-=40; const jl=document.getElementById('jumpLatest'); if(jl) jl.style.display='grid'; }
|
||||
}
|
||||
const inp=document.getElementById('msgInput'); if(inp && !isMobileUA()) inp.focus(); // #2: on phones, opening a chat should NOT pop the keyboard (user taps the box to type)
|
||||
// #7: guarantee the newest message is in view on open — force the scroll to the bottom a few times as
|
||||
// late content (avatars, images, day separators) settles, unless we deliberately stopped at a "New
|
||||
// messages" divider. This is the safety net over renderThread()'s own pin.
|
||||
[0,120,320,600].forEach((d)=>setTimeout(()=>{ if(selected&&selected.kind===kind&&selected.id===id){ const b=document.getElementById('msgs'); if(b && !b.querySelector('.new-sep')) b.scrollTop=b.scrollHeight; } }, d));
|
||||
}
|
||||
// ---------- @mentions (group chat) ----------
|
||||
let mentionItems=[], mentionIdx=0, mentionStart=-1;
|
||||
|
||||
Reference in New Issue
Block a user