diff --git a/static/js/chat.js b/static/js/chat.js index 57295cf..8ef48a9 100644 --- a/static/js/chat.js +++ b/static/js/chat.js @@ -1842,9 +1842,11 @@ if(window.visualViewport){ 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(); +// Let toolbar clicks complete before blur changes the keyboard and composer layout. +document.querySelector('.composer').addEventListener('pointerdown', function(event){ + if(event.button === 0 && document.activeElement === inputDom && event.target.closest('button')){ + event.preventDefault(); + } }); syncChatViewport(); diff --git a/tests/frontend/chat.test.cjs b/tests/frontend/chat.test.cjs index cc49c0b..ef58c0c 100644 --- a/tests/frontend/chat.test.cjs +++ b/tests/frontend/chat.test.cjs @@ -711,15 +711,20 @@ test('late AI completion does not steal focus or reopen a dismissed mobile keybo } }); -test('mobile quick questions do not summon the keyboard and tapping Send retains an active draft focus', async () => { +test('mobile quick questions keep the keyboard closed and composer controls finish clicking before blur changes layout', async () => { const app=await customerPage({human:false,width:390}); const input=app.element('input-text'); await app.context.window.fillInput('快捷咨询');await flush(); assert.equal(input.focusCalls.length,0); input.focus(); - let prevented=false; - app.element('send-button').listeners.pointerdown({button:0,preventDefault(){prevented=true;}}); - assert.ok(prevented); + for(const name of ['send-button','voice-toggle','emoji-toggle','upload-btn']){ + let prevented=false; + app.element('.composer').listeners.pointerdown({button:0,target:{closest:()=>app.element(name)},preventDefault(){prevented=true;}}); + assert.ok(prevented); + } + let blockedTextSelection=false; + app.element('.composer').listeners.pointerdown({button:0,target:{closest:()=>null},preventDefault(){blockedTextSelection=true;}}); + assert.equal(blockedTextSelection,false); assert.equal(app.context.document.activeElement,input); });