From b9acf7d9b08094cd1c057b60341495b68e9e540a Mon Sep 17 00:00:00 2001 From: BiFangKNT <1320414964@qq.com> Date: Sun, 18 Oct 2026 10:21:48 +0800 Subject: [PATCH] fix(ui): prevent closing selects from stealing focus Preserve focus when another control becomes active during the close animation. Add regression tests for filter persistence, Escape, and custom range dialog focus. --- web/src/components/ui/select.tsx | 14 ++++++++++ web/tests/e2e/monitoring-executions.spec.ts | 29 +++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/web/src/components/ui/select.tsx b/web/src/components/ui/select.tsx index 21971e51a..fd2296c08 100644 --- a/web/src/components/ui/select.tsx +++ b/web/src/components/ui/select.tsx @@ -52,6 +52,7 @@ function SelectContent({ className, children, position = 'popper', + onCloseAutoFocus, ...props }: React.ComponentProps) { return ( @@ -66,6 +67,19 @@ function SelectContent({ )} position={position} {...props} + onCloseAutoFocus={(event) => { + onCloseAutoFocus?.(event); + if (event.defaultPrevented) return; + const content = event.target as HTMLElement; + const activeElement = content.ownerDocument.activeElement; + // Exit animations may finish after another control has taken focus. + if ( + activeElement && + activeElement !== content.ownerDocument.body && + !content.contains(activeElement) + ) + event.preventDefault(); + }} > { + await installLangBotApiMocks(page, { authenticated: true }); + await page.goto('/home/monitoring'); + for (const id of ['bot', 'mode', 'status', 'time']) { + const trigger = page.locator(`#monitoring-filter-${id}`); + await trigger.click(); + await expect(page.getByRole('listbox')).toBeVisible(); + await page.keyboard.press('Escape'); + await expect(trigger).toBeFocused(); + await expect(page.getByRole('listbox')).toHaveCount(0); + } +}); + +test('keeps the custom range dialog focused when its select closes', async ({ + page, +}) => { + await installLangBotApiMocks(page, { authenticated: true }); + await page.goto('/home/monitoring'); + await page.locator('#monitoring-filter-time').click(); + await page + .getByRole('option', { name: 'Custom time range', exact: true }) + .click(); + const dialog = page.getByRole('dialog', { name: 'Custom time range' }); + await expect(dialog).toBeVisible(); + await expect(dialog.locator('input').first()).toBeFocused(); +}); + test('remembers all filters on reload and persists resetting them', async ({ page, }) => {