mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 17:37:40 +00:00
fix(mobile): keep the Android composer focused when the keyboard switches to a voice IME (#41209)
A keyboard's mic button switches to a separate voice IME, which closes the keyboard and opens the voice panel about 250 ms later. The page read every Android keyboard close as Back and blurred the field at once, so the voice IME's text had nowhere to go. Blur only once the keyboard stays closed for a second; a reopen within that window keeps focus.
This commit is contained in:
@@ -37,7 +37,7 @@ function focusedField(userAgent: string): HTMLInputElement {
|
||||
|
||||
describe('a keyboard the phone closes', () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'performance'] })
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'performance'] })
|
||||
publishShellKeyboardSource({
|
||||
read: () => shellHeight,
|
||||
subscribe: (listener) => {
|
||||
@@ -62,10 +62,24 @@ describe('a keyboard the phone closes', () => {
|
||||
expect(document.activeElement).toBe(field)
|
||||
|
||||
shellReports(0)
|
||||
vi.advanceTimersByTime(1000)
|
||||
|
||||
expect(document.activeElement).not.toBe(field)
|
||||
})
|
||||
|
||||
it('keeps the field focused when the keyboard closes and reopens for a switch to a voice IME', () => {
|
||||
const field = focusedField(ANDROID)
|
||||
startBlurringOnKeyboardClose()
|
||||
shellReports(300)
|
||||
|
||||
shellReports(0)
|
||||
vi.advanceTimersByTime(250)
|
||||
shellReports(200)
|
||||
vi.advanceTimersByTime(5000)
|
||||
|
||||
expect(document.activeElement).toBe(field)
|
||||
})
|
||||
|
||||
// The second case is tapping the underlined word after Back: that tap both focuses the field and opens the menu.
|
||||
it.each([
|
||||
['already focused', false],
|
||||
@@ -105,7 +119,7 @@ describe('a keyboard the phone closes', () => {
|
||||
target.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true }))
|
||||
vi.advanceTimersByTime(waitMs)
|
||||
shellReports(0)
|
||||
vi.advanceTimersByTime(100)
|
||||
vi.advanceTimersByTime(1000)
|
||||
|
||||
expect(document.activeElement).not.toBe(field)
|
||||
})
|
||||
|
||||
@@ -5,6 +5,8 @@ import { subscribeSoftKeyboard } from './keyboard-occlusion.web'
|
||||
// Chromium hides the IME itself to show a tapped autocorrected word's menu; Back comes with no touch.
|
||||
const FIELD_TAP_HIDE_WINDOW_MS = 1000
|
||||
const KEYBOARD_REOPEN_DELAY_MS = 50
|
||||
// A keyboard's mic switches to a voice IME, which closes the keyboard and opens again ~250 ms later; Back opens none.
|
||||
const IME_SWITCH_WINDOW_MS = 1000
|
||||
|
||||
// Android raises the IME only for a new focus session, as for the terminal live input.
|
||||
function reopenKeyboard(field: HTMLElement): void {
|
||||
@@ -14,12 +16,13 @@ function reopenKeyboard(field: HTMLElement): void {
|
||||
}
|
||||
}
|
||||
|
||||
// Android Back hides the IME without blurring; blur now so the next terminal touch cannot reopen it.
|
||||
// Android Back hides the IME without blurring; blur once it stays closed so the next terminal touch cannot reopen it.
|
||||
export function startBlurringOnKeyboardClose(): void {
|
||||
if (hostOs() !== 'android') {
|
||||
return
|
||||
}
|
||||
let lastTouch: { target: Node; at: number } | null = null
|
||||
let pendingBlur: ReturnType<typeof setTimeout> | undefined
|
||||
document.addEventListener(
|
||||
'pointerdown',
|
||||
(event) => {
|
||||
@@ -29,7 +32,7 @@ export function startBlurringOnKeyboardClose(): void {
|
||||
{ capture: true }
|
||||
)
|
||||
subscribeSoftKeyboard(
|
||||
() => {},
|
||||
() => clearTimeout(pendingBlur),
|
||||
() => {
|
||||
const field = document.activeElement
|
||||
const touch = lastTouch
|
||||
@@ -42,7 +45,12 @@ export function startBlurringOnKeyboardClose(): void {
|
||||
) {
|
||||
setTimeout(() => reopenKeyboard(field), KEYBOARD_REOPEN_DELAY_MS)
|
||||
} else {
|
||||
Keyboard.dismiss()
|
||||
// Leave alone a field focus moved to meanwhile.
|
||||
pendingBlur = setTimeout(() => {
|
||||
if (document.activeElement === field) {
|
||||
Keyboard.dismiss()
|
||||
}
|
||||
}, IME_SWITCH_WINDOW_MS)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user