mirror of
https://github.com/nesquena/hermes-webui.git
synced 2026-05-27 12:10:40 +00:00
fix(ui): smooth iPhone PWA bottom-edge bounce in chat
This commit is contained in:
+32
-1
@@ -1657,6 +1657,36 @@ let _messageUserUnpinned=false;
|
||||
let _bottomSettleToken=0;
|
||||
const NON_MESSAGE_SCROLL_INTENT_SUPPRESS_MS=350;
|
||||
const MESSAGE_UPWARD_INTENT_MS=450;
|
||||
function _isIosStandalonePwa(){
|
||||
try{
|
||||
const ua=navigator.userAgent||'';
|
||||
const isIosDevice=/iP(?:hone|ad|od)/i.test(ua)
|
||||
|| (navigator.platform==='MacIntel'&&navigator.maxTouchPoints>1);
|
||||
if(!isIosDevice) return false;
|
||||
const standalone=(typeof navigator!=='undefined'&&navigator.standalone===true)
|
||||
|| (typeof window.matchMedia==='function'&&window.matchMedia('(display-mode: standalone)').matches)
|
||||
|| (typeof window.matchMedia==='function'&&window.matchMedia('(display-mode: fullscreen)').matches);
|
||||
if(!standalone) return false;
|
||||
return typeof window.matchMedia!=='function' || window.matchMedia('(pointer: coarse)').matches;
|
||||
}catch(_){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
function _messagePanePreferredBottomScrollTop(el){
|
||||
if(!el) return 0;
|
||||
const maxTop=Math.max(0,el.scrollHeight-el.clientHeight);
|
||||
if(maxTop<=1) return maxTop;
|
||||
return _isIosStandalonePwa()?maxTop-1:maxTop;
|
||||
}
|
||||
function _maybeInsetIosStandaloneBottomEdge(el, top){
|
||||
if(!el||!_isIosStandalonePwa()) return;
|
||||
const preferredTop=_messagePanePreferredBottomScrollTop(el);
|
||||
if(preferredTop<=0||top<el.scrollHeight-el.clientHeight) return;
|
||||
_programmaticScroll=true;
|
||||
el.scrollTop=preferredTop;
|
||||
_lastScrollTop=el.scrollTop;
|
||||
requestAnimationFrame(()=>{ setTimeout(()=>{_programmaticScroll=false;},0); });
|
||||
}
|
||||
function _cancelBottomSettle(){ _bottomSettleToken++; }
|
||||
function _recordNonMessageScrollIntent(e){
|
||||
const el=document.getElementById('messages');
|
||||
@@ -1716,6 +1746,7 @@ if (typeof window !== 'undefined') window._resetScrollDirectionTracker = _resetS
|
||||
} else { _nearBottomCount=0; _scrollPinned=false; }
|
||||
if(_scrollPinned) _messageUserUnpinned=false;
|
||||
} // #1360
|
||||
_maybeInsetIosStandaloneBottomEdge(el, top);
|
||||
const btn=$('scrollToBottomBtn');
|
||||
if(btn) btn.style.display=_scrollPinned?'none':'flex';
|
||||
if(typeof _updateSessionStartJumpButton==='function') _updateSessionStartJumpButton();
|
||||
@@ -2009,7 +2040,7 @@ function _setMessageScrollToBottom(){
|
||||
const el=$('messages');
|
||||
if(!el) return;
|
||||
_programmaticScroll=true;
|
||||
el.scrollTop=el.scrollHeight;
|
||||
el.scrollTop=_messagePanePreferredBottomScrollTop(el);
|
||||
_lastScrollTop=el.scrollTop;
|
||||
_nearBottomCount=2;
|
||||
_scrollPinned=true;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
from pathlib import Path
|
||||
|
||||
REPO = Path(__file__).parent.parent
|
||||
UI_JS = (REPO / 'static' / 'ui.js').read_text(encoding='utf-8')
|
||||
|
||||
|
||||
def test_ios_standalone_detection_helper_exists():
|
||||
assert 'function _isIosStandalonePwa()' in UI_JS
|
||||
assert "window.matchMedia('(display-mode: standalone)').matches" in UI_JS
|
||||
assert 'navigator.standalone===true' in UI_JS
|
||||
|
||||
|
||||
def test_message_bottom_prefers_one_pixel_inset_on_ios_pwa():
|
||||
assert 'function _messagePanePreferredBottomScrollTop(el)' in UI_JS
|
||||
assert 'return _isIosStandalonePwa()?maxTop-1:maxTop;' in UI_JS
|
||||
assert 'el.scrollTop=_messagePanePreferredBottomScrollTop(el);' in UI_JS
|
||||
|
||||
|
||||
def test_ios_pwa_bottom_edge_guard_installed_on_messages_pane():
|
||||
assert 'function _maybeInsetIosStandaloneBottomEdge(el, top)' in UI_JS
|
||||
assert 'if(preferredTop<=0||top<el.scrollHeight-el.clientHeight) return;' in UI_JS
|
||||
assert '_maybeInsetIosStandaloneBottomEdge(el, top);' in UI_JS
|
||||
Reference in New Issue
Block a user