fix(mobile): image pager direction + end-arrows; reset --kb on composer blur
- Pager: force the staged (opposite-edge) frame to PAINT (void offsetWidth) before animating in, so the incoming image enters from the correct side (was appearing from the exit side). Hide prev-arrow at the first image and next-arrow at the last. - #1: keyboard closed after reply-cancel but the --kb lift stayed (empty gap). Add keyboardDidHide reset + a composer-blur fallback that drops --kb when focus leaves the composer. Build marker -> 2026-07-18-batch119. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+16
-10
@@ -1134,7 +1134,7 @@
|
||||
</head>
|
||||
<body>
|
||||
<script src="/icons.js?v=6"></script>
|
||||
<script>window.__BUILD='2026-07-18-batch118';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
||||
<script>window.__BUILD='2026-07-18-batch119';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
|
||||
@@ -1156,10 +1156,12 @@ function twemojify(_el){ /* native emoji: nothing to do */ }</script>
|
||||
// fires at the START of the animation, so the CSS bottom-transition slides the composer up WITH the
|
||||
// keyboard (smooth). raw>innerHeight guards the rare device-pixel unit.
|
||||
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.scrollTop=b.scrollHeight; }
|
||||
window.__bzResetKb=function(){ setKb(0); }; // called on composer blur as a safety net (#1: keyboard closed but lift stayed, leaving empty space after reply-cancel)
|
||||
var KB=C.Plugins&&C.Plugins.Keyboard;
|
||||
if(KB && KB.addListener){
|
||||
KB.addListener('keyboardWillShow', function(info){ var raw=(info&&info.keyboardHeight)||0; var kb = raw>window.innerHeight ? raw/(window.devicePixelRatio||1) : raw; setKb(kb); if(window.bzDbg) bzDbg('kbShow',{raw:raw, applied:Math.round(kb)}); });
|
||||
KB.addListener('keyboardWillHide', function(){ setKb(0); });
|
||||
KB.addListener('keyboardDidHide', function(){ setKb(0); }); // belt-and-suspenders: some close paths only fire Did*
|
||||
}
|
||||
}catch(_){} })();
|
||||
</script>
|
||||
@@ -2634,22 +2636,26 @@ function openLightbox(src){
|
||||
},{passive:false});
|
||||
img.addEventListener('touchend',()=>{ pinchD=0; });
|
||||
|
||||
const show=(i)=>{ if(!multi) return; idx=Math.max(0, Math.min(gallery.length-1, i)); const u=gallery[idx]; img.src=u; if(dl) dl.setAttribute('href',u); resetZoom(); }; // clamp (no wrap → no blank slide past the last)
|
||||
const syncArrows=()=>{ if(!multi) return; const pB=ov.querySelector('.lb-prev'), nB=ov.querySelector('.lb-next'); if(pB) pB.style.visibility=idx<=0?'hidden':'visible'; if(nB) nB.style.visibility=idx>=gallery.length-1?'hidden':'visible'; };
|
||||
const show=(i)=>{ if(!multi) return; idx=Math.max(0, Math.min(gallery.length-1, i)); const u=gallery[idx]; img.src=u; if(dl) dl.setAttribute('href',u); resetZoom(); syncArrows(); }; // clamp (no wrap → no blank slide past the last) + hide the end arrow
|
||||
const close=()=>{ ov.remove(); document.removeEventListener('keydown', onKey); };
|
||||
// #8: animated pager — the current image slides fully out in the swipe direction, then the next/prev
|
||||
// image slides in from the OPPOSITE side (instead of an instant src swap). Stops at the first/last.
|
||||
const pagerGo=(dir)=>{ // dir:+1 next (out to the left), -1 prev (out to the right)
|
||||
// image slides in from the OPPOSITE side. Stops at the first/last.
|
||||
const pagerGo=(dir)=>{ // dir:+1 next (current exits left, next enters from right), -1 prev (mirror)
|
||||
const target = idx + (dir>0?1:-1);
|
||||
if(!multi || target<0 || target>=gallery.length){ img.style.transition='transform .18s'; img.style.transform=''; return; } // at an edge → spring back
|
||||
const W=(window.innerWidth||400); let done=false;
|
||||
const finish=()=>{ if(done) return; done=true; img.removeEventListener('transitionend', finish);
|
||||
show(target); // load the target image; resetZoom clears its transform
|
||||
img.style.transition='none'; img.style.transform='translateX('+(dir>0?W:-W)+'px)'; // stage it off the opposite edge
|
||||
requestAnimationFrame(()=>{ img.style.transition='transform .2s cubic-bezier(.3,.72,.3,1)'; img.style.transform='translateX(0)'; setTimeout(()=>{ if(img){ img.style.transition=''; img.style.transform=''; } ov.style.background=''; }, 220); });
|
||||
show(target); // load the target image; resetZoom clears its transform
|
||||
img.style.transition='none'; img.style.transform='translateX('+(dir>0?W:-W)+'px)'; // stage off the OPPOSITE edge (next=right, prev=left)
|
||||
void img.offsetWidth; // force that staged position to PAINT, else the browser animates from the exit side (wrong direction)
|
||||
img.style.transition='transform .22s cubic-bezier(.3,.72,.3,1)'; img.style.transform='translateX(0)';
|
||||
setTimeout(()=>{ if(img){ img.style.transition=''; img.style.transform=''; } ov.style.background=''; }, 240);
|
||||
};
|
||||
img.style.transition='transform .16s ease-out'; img.style.transform='translateX('+(dir>0?-W:W)+'px)'; // current slides out
|
||||
img.addEventListener('transitionend', finish); setTimeout(finish, 190); // fallback if transitionend misses
|
||||
img.style.transition='transform .16s ease-out'; img.style.transform='translateX('+(dir>0?-W:W)+'px)'; // current slides out (next→left, prev→right)
|
||||
img.addEventListener('transitionend', finish); setTimeout(finish, 180); // fallback if transitionend misses
|
||||
};
|
||||
syncArrows(); // initial: hide prev at first / next at last
|
||||
// #8: swipe DOWN (when not zoomed) to dismiss, like native photo viewers — drag follows the finger and
|
||||
// fades the backdrop; release past a threshold closes, otherwise it springs back.
|
||||
let dsy=0, dsx=0, dDrag=false;
|
||||
@@ -2775,7 +2781,7 @@ async function openConvo(kind,id){
|
||||
const _d=getDraft(kind,id); if(_d){ inpEl.value=_d; autoGrow(inpEl); } // restore unsent draft
|
||||
inpEl.addEventListener('paste', onPaste);
|
||||
inpEl.addEventListener('input', ()=>{ maybeAutocorrect(inpEl); autoGrow(inpEl); setDraft(kind,id,inpEl.value); noteTyping(); }); // auto-correct common typos, keep the draft in sync + emit "typing…"
|
||||
inpEl.addEventListener('blur', stopTyping);
|
||||
inpEl.addEventListener('blur', ()=>{ stopTyping(); setTimeout(()=>{ const a=document.activeElement; const stillTyping=a&&(a.tagName==='TEXTAREA'||a.tagName==='INPUT'); if(!stillTyping && window.__bzResetKb) window.__bzResetKb(); }, 120); }); // #1: composer lost focus → keyboard is closing → drop the --kb lift so no empty gap remains
|
||||
inpEl.addEventListener('keydown', (e)=>{ if(e.key==='Enter' && !e.shiftKey){ if(mentionItems && mentionItems.length) return; e.preventDefault(); sendMessage(); } }); // Enter sends, Shift+Enter = newline
|
||||
// Desktop: LEFT-click a red-squiggled word → native spelling suggestions right there (the shell
|
||||
// pops a suggestions menu only if the clicked word is actually misspelled). No right-click needed.
|
||||
|
||||
Reference in New Issue
Block a user