mobile: iOS PWA keyboard — pin app to visual viewport + undo webview scroll so header stays; swipe-back telemetry (batch140)

This commit is contained in:
2026-07-19 23:17:13 +05:30
parent 578f3a2921
commit 9c731f087e
+23 -5
View File
@@ -1158,7 +1158,7 @@
</head> </head>
<body> <body>
<script src="/icons.js?v=6"></script> <script src="/icons.js?v=6"></script>
<script>window.__BUILD='2026-07-19-batch139';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD); <script>window.__BUILD='2026-07-19-batch140';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. // 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 // We used to run Twemoji over every emoji, which swapped each one for an <img> pulled INDIVIDUALLY from
@@ -1212,11 +1212,27 @@ window.__perfProbe=function(label){ try{
// the Safari toolbar growing/shrinking (only a real keyboard shrinks the visual viewport that much). // the Safari toolbar growing/shrinking (only a real keyboard shrinks the visual viewport that much).
(function(){ try{ (function(){ try{
var C=window.Capacitor; if(C && C.isNativePlatform && C.isNativePlatform()) return; var C=window.Capacitor; if(C && C.isNativePlatform && C.isNativePlatform()) return;
var vv=window.visualViewport; if(!vv) return; var r=document.documentElement; var vv=window.visualViewport; if(!vv) return; var r=document.documentElement, b=document.body;
function setKb(px){ px=Math.max(0,Math.round(px)); r.style.setProperty('--kb',px+'px'); document.body.classList.toggle('kb-open',px>4); var b=document.getElementById('msgs'); if(b && (b.scrollHeight-b.scrollTop-b.clientHeight)<400) b.scrollTop=b.scrollHeight; } function pinMsgs(){ var m=document.getElementById('msgs'); if(m && (m.scrollHeight-m.scrollTop-m.clientHeight)<400) m.scrollTop=m.scrollHeight; }
function upd(){ var d=document.documentElement.clientHeight - vv.height - vv.offsetTop; setKb(d>100?d:0); } function upd(){
var cover = r.clientHeight - vv.height; // how much of the LAYOUT viewport the keyboard covers
if(cover > 100){
// Keyboard open. iOS/Safari does NOT resize the layout viewport (interactive-widget is ignored) and the
// webview SCROLLS to reveal the focused box (device log: scrollY≈413), which pushes the header off the
// top. Fix: pin the whole app to the VISUAL viewport height and undo the scroll, so header + messages +
// composer all fit above the keyboard. On Android, interactive-widget=resizes-content already shrank
// clientHeight to vv.height, so cover≈0 and this branch is skipped (that path is already correct).
b.style.height = vv.height + 'px'; b.style.bottom = 'auto';
r.style.setProperty('--kb','0px'); document.body.classList.add('kb-open');
if(window.scrollY || vv.offsetTop) window.scrollTo(0,0);
pinMsgs();
} else {
b.style.height=''; b.style.bottom='';
r.style.setProperty('--kb','0px'); document.body.classList.remove('kb-open');
}
}
vv.addEventListener('resize', upd); vv.addEventListener('scroll', upd); vv.addEventListener('resize', upd); vv.addEventListener('scroll', upd);
window.__bzResetKb=function(){ setKb(0); }; window.__bzResetKb=function(){ b.style.height=''; b.style.bottom=''; r.style.setProperty('--kb','0px'); document.body.classList.remove('kb-open'); };
}catch(_){} })(); }catch(_){} })();
</script> </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> <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>
@@ -4984,6 +5000,7 @@ window.addEventListener('popstate', ()=>{ if(bzcBack()){ try{ history.pushState(
document.body.classList.add('chat-dragging'); document.body.classList.add('chat-dragging');
if(content) content.style.transition='none'; if(content) content.style.transition='none';
if(chatcol) chatcol.style.transition='none'; if(chatcol) chatcol.style.transition='none';
if(window.bzDbg) window.bzDbg('swipe',{s:'confirm',hasC:content?1:0,hasL:chatcol?1:0,sx:Math.round(sx),dx:Math.round(dx),w:Math.round(w),cCls:content?String(content.className).slice(0,20):'NULL'}); // TEMP
} else return; } else return;
} }
e.preventDefault(); // needs passive:false — claims the gesture e.preventDefault(); // needs passive:false — claims the gesture
@@ -4995,6 +5012,7 @@ window.addEventListener('popstate', ()=>{ if(bzcBack()){ try{ history.pushState(
if(!dragging){ armed=false; return; } if(!dragging){ armed=false; return; }
const t=(e.changedTouches&&e.changedTouches[0]); const t=(e.changedTouches&&e.changedTouches[0]);
const dx=t?Math.max(0,t.clientX-sx):0, dt=Math.max(1,Date.now()-t0), v=dx/dt; const dx=t?Math.max(0,t.clientX-sx):0, dt=Math.max(1,Date.now()-t0), v=dx/dt;
if(window.bzDbg) window.bzDbg('swipe',{s:'end',dx:Math.round(dx),w:Math.round(w),v:Math.round(v*100)/100,done:(dx>w*0.35||(v>0.5&&dx>40))?1:0}); // TEMP
if(dx>w*0.35 || (v>0.5 && dx>40)){ // complete the pop if(dx>w*0.35 || (v>0.5 && dx>40)){ // complete the pop
if(content){ content.style.transition='transform .17s cubic-bezier(.4,0,.7,.4)'; content.style.transform='translate3d(100%,0,0)'; } if(content){ content.style.transition='transform .17s cubic-bezier(.4,0,.7,.4)'; content.style.transform='translate3d(100%,0,0)'; }
if(chatcol){ chatcol.style.transition='transform .17s cubic-bezier(.4,0,.7,.4)'; chatcol.style.transform='translate3d(0,0,0)'; } if(chatcol){ chatcol.style.transition='transform .17s cubic-bezier(.4,0,.7,.4)'; chatcol.style.transform='translate3d(0,0,0)'; }