feat(mobile): finger-following swipe-back (iOS-style), reusing the working close layout
The conversation pane now tracks your finger from the left edge and reveals the chat list behind it, completing the back past ~35% width or springing back otherwise. Reuses the existing body.chat-dragging layout (already defined, identical to chat-closing that showWelcome uses): content z-index:2 at translateX(0), list .chatcol absolute behind at z-index:0 — so the pane starts at the correct on-screen origin (the earlier attempt's 'one screen-width off' bug was a different setup). Vertical drags still scroll; popup/ search edge-release still closes via bzcBack. build batch154. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+48
-12
@@ -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-22-batch153';console.log('%cBiz Connect','color:#1F3B73;font-weight:bold','build '+window.__BUILD);
|
<script>window.__BUILD='2026-07-22-batch154';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
|
||||||
@@ -5016,21 +5016,57 @@ function bzcBack(){
|
|||||||
}
|
}
|
||||||
try{ history.pushState(null,'',location.href); }catch(_){}
|
try{ history.pushState(null,'',location.href); }catch(_){}
|
||||||
window.addEventListener('popstate', ()=>{ if(bzcBack()){ try{ history.pushState(null,'',location.href); }catch(_){} } });
|
window.addEventListener('popstate', ()=>{ if(bzcBack()){ try{ history.pushState(null,'',location.href); }catch(_){} } });
|
||||||
// #4: left-edge SWIPE-back. A drag that begins at the very left edge and releases clearly rightward closes
|
// #4: left-edge SWIPE-back. In an open conversation the pane FOLLOWS your finger and reveals the chat list
|
||||||
// the topmost layer (popup → in-conversation search → open chat) via bzcBack() → showWelcome()'s parallax
|
// behind it (iOS-style); past ~35% of the width it completes the back, otherwise it springs back. It reuses
|
||||||
// slide. RELEASE-triggered on purpose: the finger-following version had a layout bug (the conversation pane
|
// the SAME layout showWelcome()'s close slide uses (body.chat-dragging: content z-index:2 at translateX(0),
|
||||||
// sat one screen-width off, so it couldn't be dragged into view) that I could not resolve without seeing it
|
// the list .chatcol absolute behind at z-index:0), so the pane tracks the finger from the correct on-screen
|
||||||
// render on-device. This version reliably runs the same push-slide close.
|
// origin — the earlier version's "pane one screen-width off" bug came from a different setup. When a popup /
|
||||||
|
// in-conversation search is open instead, a rightward edge release just closes that (release-based bzcBack).
|
||||||
(function(){
|
(function(){
|
||||||
let sx=0, sy=0, edge=false;
|
let sx=0, sy=0, edge=false, mode='', dragging=false, content=null, W=0;
|
||||||
|
const mobile=()=>window.matchMedia('(max-width:760px)').matches;
|
||||||
|
function reset(){ edge=false; mode=''; dragging=false; content=null; }
|
||||||
window.addEventListener('touchstart',(e)=>{
|
window.addEventListener('touchstart',(e)=>{
|
||||||
if(e.touches.length!==1){ edge=false; return; }
|
reset();
|
||||||
const t=e.touches[0]; edge = t.clientX<=24; sx=t.clientX; sy=t.clientY; // only arm when starting at the left edge
|
if(e.touches.length!==1) return;
|
||||||
|
const t=e.touches[0];
|
||||||
|
if(t.clientX>24 || !mobile()) return; // must start at the very left edge, on mobile
|
||||||
|
const overlay=document.querySelector('.modal-ov');
|
||||||
|
const sh=document.getElementById('convoSearchHead'); const searchOpen=sh && sh.style.display!=='none';
|
||||||
|
if(overlay || searchOpen){ edge=true; mode='back'; sx=t.clientX; sy=t.clientY; return; } // release-based close
|
||||||
|
if(!selected) return; // only drag when a conversation is open
|
||||||
|
edge=true; mode='drag'; sx=t.clientX; sy=t.clientY; W=window.innerWidth||360;
|
||||||
}, {passive:true});
|
}, {passive:true});
|
||||||
|
window.addEventListener('touchmove',(e)=>{
|
||||||
|
if(!edge || mode!=='drag' || e.touches.length!==1) return;
|
||||||
|
const t=e.touches[0], dx=t.clientX-sx, dy=t.clientY-sy;
|
||||||
|
if(!dragging){
|
||||||
|
if(Math.abs(dy)>Math.abs(dx) && Math.abs(dy)>10){ reset(); return; } // vertical → let the messages scroll
|
||||||
|
if(dx<8) return; // wait for a clear rightward intent
|
||||||
|
dragging=true;
|
||||||
|
renderChats(searchVal()); // populate the list underneath before revealing it
|
||||||
|
document.body.classList.add('chat-dragging'); // content on top (z:2), list absolute behind (z:0)
|
||||||
|
content=document.querySelector('.shell > .content');
|
||||||
|
if(content) content.style.transition='none';
|
||||||
|
}
|
||||||
|
if(content) content.style.transform='translateX('+Math.max(0,dx)+'px)';
|
||||||
|
if(e.cancelable) e.preventDefault(); // stop the message list scrolling under the drag
|
||||||
|
}, {passive:false});
|
||||||
window.addEventListener('touchend',(e)=>{
|
window.addEventListener('touchend',(e)=>{
|
||||||
if(!edge) return; edge=false;
|
if(!edge) return;
|
||||||
const t=e.changedTouches&&e.changedTouches[0]; if(!t) return;
|
const m=mode, wasDragging=dragging, c=content;
|
||||||
if((t.clientX-sx)>60 && Math.abs(t.clientY-sy)<50) bzcBack(); // rightward, mostly-horizontal swipe = back
|
const t=e.changedTouches&&e.changedTouches[0];
|
||||||
|
const dx=t?(t.clientX-sx):0, dy=t?(t.clientY-sy):0;
|
||||||
|
reset();
|
||||||
|
if(m==='back'){ if(dx>60 && Math.abs(dy)<50) bzcBack(); return; }
|
||||||
|
if(!wasDragging) return; // never became a horizontal drag
|
||||||
|
if(dx > Math.min(140, W*0.35)){ // far enough → complete the back
|
||||||
|
if(c){ c.style.transition='transform .18s cubic-bezier(.4,0,.7,.4)'; requestAnimationFrame(()=>{ c.style.transform='translateX(100%)'; }); }
|
||||||
|
setTimeout(()=>{ if(c){ c.style.transition=''; c.style.transform=''; } showWelcome(true); }, 185);
|
||||||
|
} else { // not far enough → spring back to the conversation
|
||||||
|
if(c){ c.style.transition='transform .18s ease-out'; requestAnimationFrame(()=>{ c.style.transform='translateX(0)'; }); }
|
||||||
|
setTimeout(()=>{ if(c){ c.style.transition=''; c.style.transform=''; } document.body.classList.remove('chat-dragging'); }, 185);
|
||||||
|
}
|
||||||
}, {passive:true});
|
}, {passive:true});
|
||||||
})();
|
})();
|
||||||
// #9: swipe a message bubble to the right to reply to it (mobile), like WhatsApp/Teams. The bubble
|
// #9: swipe a message bubble to the right to reply to it (mobile), like WhatsApp/Teams. The bubble
|
||||||
|
|||||||
Reference in New Issue
Block a user