修复手机键盘弹出时页面位移与焦点跳转
This commit is contained in:
+16
-9
@@ -1,5 +1,5 @@
|
||||
/* Customer workspace: navigation, welcome, conversation and composer. */
|
||||
body { padding: 20px; height: 100dvh; overflow: hidden; }
|
||||
body { padding: 20px; height: 100vh; height: 100dvh; overflow: hidden; }
|
||||
.chat-container {
|
||||
display: flex; width: min(1440px, 100%); height: 100%; margin: auto;
|
||||
overflow: hidden; position: relative; border: 1px solid var(--border);
|
||||
@@ -44,7 +44,7 @@ body { padding: 20px; height: 100dvh; overflow: hidden; }
|
||||
.clear-all-btn:hover { color: var(--danger); }
|
||||
.sidebar-note { display: flex; align-items: center; gap: 7px; margin-top: 10px; color: var(--muted); font-size: 10px; }
|
||||
.left-panel-hide { width: 0 !important; padding-left: 0 !important; padding-right: 0 !important; visibility: hidden; border: 0; }
|
||||
.main-chat { flex: 1; min-width: 0; display: flex; flex-direction: column; position: relative; overflow: hidden; }
|
||||
.main-chat { flex: 1; min-width: 0; min-height: 0; display: flex; flex-direction: column; position: relative; overflow: hidden; }
|
||||
.chat-title { flex-shrink: 0; min-height: 75px; padding: 16px 26px; display: flex; align-items: center; gap: 13px; border-bottom: 1px solid var(--border); background: var(--surface); z-index: 31; }
|
||||
.header-copy { min-width: 0; flex: 1; }
|
||||
.header-kicker { font-size: 10px; color: var(--muted); letter-spacing: .06em; margin-bottom: 3px; }
|
||||
@@ -205,17 +205,24 @@ body { padding: 20px; height: 100dvh; overflow: hidden; }
|
||||
.suggest-copy small { font-size: 11px; }
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
body { padding: 0; }
|
||||
.chat-container { border: 0; border-radius: 0; height: 100dvh; }
|
||||
.left-panel { position: absolute; left: 0; top: 72px; bottom: 0; width: 270px; max-width: 85vw; z-index: 30; box-shadow: 15px 0 40px rgba(20, 35, 60, .12); }
|
||||
body { padding: 0; position: fixed; inset: 0; width: 100%; overscroll-behavior: none; }
|
||||
.chat-container { --chat-header-height: 72px; position: fixed; left: 0; right: 0; top: 0; margin: 0; width: 100%; border: 0; border-radius: 0; height: 100vh; height: 100dvh; padding: env(safe-area-inset-top) env(safe-area-inset-right) 0 env(safe-area-inset-left); }
|
||||
.left-panel { position: absolute; left: 0; top: calc(var(--chat-header-height) + env(safe-area-inset-top)); bottom: 0; width: 270px; max-width: 85vw; z-index: 30; box-shadow: 15px 0 40px rgba(20, 35, 60, .12); }
|
||||
.left-panel .brand { display: none; }
|
||||
.side-closed .left-panel { transform: translateX(-105%); visibility: hidden; pointer-events: none; }
|
||||
.chat-container:not(.side-closed) .sidebar-backdrop { display: block; position: absolute; inset: 72px 0 0; border: 0; z-index: 20; background: rgba(16, 30, 52, .25); }
|
||||
.chat-container:not(.side-closed) .sidebar-backdrop { display: block; position: absolute; inset: calc(var(--chat-header-height) + env(safe-area-inset-top)) 0 0; border: 0; z-index: 20; background: rgba(16, 30, 52, .25); }
|
||||
.main-chat { width: 100%; }
|
||||
.chat-title { min-height: 72px; height: 72px; padding: 13px 18px; gap: 11px; }
|
||||
#chatDom { padding: 24px 20px 15px; }
|
||||
.chat-title { min-height: var(--chat-header-height); height: var(--chat-header-height); padding: 13px 18px; gap: 11px; }
|
||||
#chatDom { padding: 24px 20px 15px; overscroll-behavior-y: contain; }
|
||||
.ai-welcome-card { padding-top: 20px; }
|
||||
.composer { width: calc(100% - 40px); }
|
||||
.composer { width: calc(100% - 40px); padding-bottom: calc(10px + env(safe-area-inset-bottom)); }
|
||||
.chat-container.is-composing { --chat-header-height: 56px; }
|
||||
.is-composing .chat-title { padding-top: 8px; padding-bottom: 8px; }
|
||||
.is-composing .composer { padding-top: 6px; padding-bottom: 6px; }
|
||||
.is-composing .composer-footer { display: none; }
|
||||
#input-text, .history-search { font-size: max(16px, 1em); }
|
||||
.history-search { height: 44px; }
|
||||
.history-search-wrap svg { top: 14px; }
|
||||
.user-message, .robot-message { max-width: 94%; }
|
||||
.del-history-btn { opacity: 1; }
|
||||
.voice-toggle { display: grid; place-items: center; flex: 0 0 40px; height: 36px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); color: var(--brand-text); font-size: 20px; }
|
||||
|
||||
+46
-11
@@ -6,6 +6,11 @@ const sendBtn = document.getElementById("send-button");
|
||||
const uploadBtn = document.getElementById("upload-btn");
|
||||
const uploadInput = document.getElementById("upload-input");
|
||||
const composerStatus = document.getElementById("composer-status");
|
||||
function focusComposer(force){
|
||||
if(force || !isNarrow() || document.activeElement === inputDom){
|
||||
inputDom.focus({preventScroll:true});
|
||||
}
|
||||
}
|
||||
let uploadPending = false;
|
||||
let uploadVersion = 0;
|
||||
let uploadController = null;
|
||||
@@ -121,12 +126,12 @@ uploadInput.onchange = function(){
|
||||
remove.className = "attachment-remove";
|
||||
remove.textContent = "×";
|
||||
remove.setAttribute("aria-label", t("removeAttachment"));
|
||||
remove.onclick = function(){ clearAttachment(); inputDom.focus(); };
|
||||
remove.onclick = function(){ clearAttachment(); focusComposer(true); };
|
||||
prev.append(copy, remove);
|
||||
var area = document.querySelector(".input-area");
|
||||
area.before(prev);
|
||||
setComposerStatus("");
|
||||
inputDom.focus();
|
||||
focusComposer();
|
||||
}
|
||||
})
|
||||
.catch(function(error){
|
||||
@@ -1140,7 +1145,7 @@ async function returnToAI(){
|
||||
stopHumanMode();
|
||||
setComposerStatus('');
|
||||
addMsg(t('humanClosed'), false);
|
||||
inputDom.focus();
|
||||
focusComposer();
|
||||
}catch(error){
|
||||
if(version === conversationVersion) setComposerStatus(t('humanReturnFailed'), true);
|
||||
}finally{
|
||||
@@ -1166,7 +1171,7 @@ async function sendHumanMessage(content){
|
||||
if(inputDom.value === draft){ inputDom.value = ''; autoGrow(); }
|
||||
if(window.__pendingImg === attachment) clearAttachment();
|
||||
setComposerStatus(t(humanStatus === 'active' ? 'humanSent' : 'humanWaiting'));
|
||||
inputDom.focus();
|
||||
focusComposer();
|
||||
return true;
|
||||
}catch(error){
|
||||
if(version !== conversationVersion || humanSendingToken !== token) return;
|
||||
@@ -1471,7 +1476,7 @@ async function doSend(retryText){
|
||||
var tag = window.__pendingTag || "[图片]";
|
||||
const draft = inputDom.value, attachmentVersion = uploadVersion;
|
||||
if(!sendWord && !imgUrl){
|
||||
inputDom.focus();
|
||||
focusComposer(true);
|
||||
return;
|
||||
}
|
||||
if(uploadPending){ setComposerStatus(t("uploading")); return; }
|
||||
@@ -1504,7 +1509,7 @@ async function doSend(retryText){
|
||||
addMsg(fullContent, true);
|
||||
if(inputDom.value === draft){ inputDom.value = ""; autoGrow(); }
|
||||
if(attachmentVersion === uploadVersion) clearAttachment();
|
||||
inputDom.focus();
|
||||
focusComposer();
|
||||
}
|
||||
|
||||
const aiBubble = addMsg(t("thinking"), false);
|
||||
@@ -1537,7 +1542,6 @@ async function doSend(retryText){
|
||||
setBusy(false);
|
||||
aiBubble.classList.remove("typing");
|
||||
if(activeBubble === aiBubble) activeBubble = null;
|
||||
inputDom.focus();
|
||||
|
||||
let raw = aiBubble.dataset.raw || "";
|
||||
// 模型有时会把卡片标记原样输出,这里兜底转成时间轴
|
||||
@@ -1719,7 +1723,10 @@ function autoGrow(){
|
||||
return;
|
||||
}
|
||||
inputDom.style.height = "auto";
|
||||
inputDom.style.height = Math.min(inputDom.scrollHeight, 140) + "px";
|
||||
const viewport = window.visualViewport;
|
||||
const height = viewport && viewport.scale === 1 ? viewport.height : window.innerHeight;
|
||||
const limit = isNarrow() && height > 0 ? Math.min(140, Math.max(44, Math.floor(height / 4))) : 140;
|
||||
inputDom.style.height = Math.min(inputDom.scrollHeight, limit) + "px";
|
||||
}
|
||||
inputDom.addEventListener("input", autoGrow);
|
||||
|
||||
@@ -1777,12 +1784,30 @@ renderSidebar();
|
||||
|
||||
function syncChatViewport(){
|
||||
const viewport = window.visualViewport;
|
||||
chatContainer.style.height = viewport && isNarrow() && viewport.scale === 1
|
||||
? Math.round(viewport.height) + 'px' : '';
|
||||
const narrow = isNarrow();
|
||||
const unzoomed = !viewport || Math.abs(viewport.scale - 1) < 0.01;
|
||||
if(narrow && unzoomed){
|
||||
const height = viewport ? viewport.height : window.innerHeight;
|
||||
if(Number.isFinite(height) && height > 0){
|
||||
chatContainer.style.height = Math.round(height) + 'px';
|
||||
// Keyboard focus may pan the visual viewport without changing its height.
|
||||
chatContainer.style.top = Math.max(0, Math.round(viewport && viewport.offsetTop || 0)) + 'px';
|
||||
}
|
||||
}else{
|
||||
chatContainer.style.height = '';
|
||||
chatContainer.style.top = '';
|
||||
}
|
||||
chatContainer.classList.toggle('is-composing', narrow && document.activeElement === inputDom);
|
||||
autoGrow();
|
||||
if(!chatDom.querySelector('.ai-welcome-card')) scrollChatToEnd();
|
||||
updateToBottom();
|
||||
}
|
||||
let viewportSyncQueued = false;
|
||||
function scheduleChatViewport(){
|
||||
if(viewportSyncQueued) return;
|
||||
viewportSyncQueued = true;
|
||||
requestAnimationFrame(function(){ viewportSyncQueued = false; syncChatViewport(); });
|
||||
}
|
||||
|
||||
toggleBtn.onclick = function(){
|
||||
sideOpen = !sideOpen;
|
||||
@@ -1810,7 +1835,17 @@ window.addEventListener("resize", function(){
|
||||
renderSidebar();
|
||||
syncChatViewport();
|
||||
});
|
||||
if(window.visualViewport) window.visualViewport.addEventListener('resize', syncChatViewport);
|
||||
if(window.visualViewport){
|
||||
window.visualViewport.addEventListener('resize', scheduleChatViewport);
|
||||
window.visualViewport.addEventListener('scroll', scheduleChatViewport);
|
||||
}
|
||||
window.addEventListener('pageshow', scheduleChatViewport);
|
||||
inputDom.addEventListener('focus', scheduleChatViewport);
|
||||
inputDom.addEventListener('blur', scheduleChatViewport);
|
||||
// Keep an already open keyboard while tapping Send; keyboard navigation is unchanged.
|
||||
sendBtn.addEventListener('pointerdown', function(event){
|
||||
if(event.button === 0 && document.activeElement === inputDom) event.preventDefault();
|
||||
});
|
||||
syncChatViewport();
|
||||
|
||||
voiceInput = ChatVoice.create({
|
||||
|
||||
+1
-1
@@ -168,7 +168,7 @@ var ChatVoice = {
|
||||
toggle.onclick = function(){
|
||||
if(!mode && !supported()) return;
|
||||
setMode(!mode);
|
||||
if(!mode) input.focus();
|
||||
if(!mode) input.focus({preventScroll:true});
|
||||
};
|
||||
if(window.PointerEvent){
|
||||
hold.addEventListener('pointerdown', function(event){
|
||||
|
||||
Reference in New Issue
Block a user