From 90fc77f203f2f266c761c573430ff48746cc0cad Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Sat, 15 Aug 2026 02:00:54 +0200 Subject: [PATCH] fix(accounts): release row focus after Escape Blur the focused account row only when Escape clears an active account selection. This prevents Chromium focus-visible styling from leaving a thick clipped blue outline while retaining accessible focus indicators during normal keyboard navigation. --- src/renderer/App.tsx | 9 ++++++--- src/renderer/selection.ts | 10 ++++++++++ tests/selection.test.ts | 17 +++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 07f4678..e63c2b0 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -39,7 +39,7 @@ import { getProviderUsageDayKey } from "../shared/provider-daily-limits"; import { preservePackageOrderForDisplay, sortPackageOrderByName } from "./package-order"; -import { pruneSelection, resolveEscapeSelectionScope, shouldClearDownloadSelection } from "./selection"; +import { pruneSelection, releaseAccountSelectionFocus, resolveEscapeSelectionScope, shouldClearDownloadSelection } from "./selection"; import { buildConfiguredProviderOrder, buildScopedAccountEnabledState, getAccountDialogSelectableOptions, matchesAccountModeFilter, pruneAccountRowSelections, resolveAccountStatusState, resolveAccountUsername, resolveVisibleAccountKind, runOptimisticAccountUpdate, updateAccountRowSelection } from "./account-ui"; import type { AccountModeFilter } from "./account-ui"; import { buildAccountDeleteCommand, buildAccountReplaceCommand, buildAccountSecretRequest, createAccountEditState, validateAccountEdit } from "./account-edit"; @@ -4161,7 +4161,10 @@ export function App(): ReactElement { if (document.querySelector(".ctx-menu") || document.querySelector(".modal-backdrop")) return; if (selectionScope === "downloads") setSelectedIds(new Set()); else if (selectionScope === "history") setSelectedHistoryIds(new Set()); - else setSelectedAccountRowKeys(new Set()); + else if (selectedAccountRowKeys.size > 0) { + setSelectedAccountRowKeys(new Set()); + releaseAccountSelectionFocus(document.activeElement instanceof HTMLElement ? document.activeElement : null); + } } } if (e.key === "Delete" && tabRef.current === "downloads" && selectedIds.size > 0) { @@ -4179,7 +4182,7 @@ export function App(): ReactElement { window.addEventListener("keydown", onKey); window.addEventListener("mousedown", onDown); return () => { window.removeEventListener("keydown", onKey); window.removeEventListener("mousedown", onDown); }; - }, [requestDeleteSelection, selectedIds, settingsSubTab]); + }, [requestDeleteSelection, selectedAccountRowKeys, selectedIds, settingsSubTab]); const onExportBackup = async (): Promise => { closeMenus(); diff --git a/src/renderer/selection.ts b/src/renderer/selection.ts index b423885..4e403be 100644 --- a/src/renderer/selection.ts +++ b/src/renderer/selection.ts @@ -28,6 +28,16 @@ export function resolveEscapeSelectionScope( return view === "settings" && settingsSection === "accounts" ? "accounts" : null; } +export function releaseAccountSelectionFocus( + activeElement: { closest: (selector: string) => unknown; blur: () => void } | null +): boolean { + if (!activeElement?.closest(".settings-account-row")) { + return false; + } + activeElement.blur(); + return true; +} + /** * Drop selected ids whose package OR item no longer exists in the session. * The selection set mixes package and item ids; when entries vanish (delta diff --git a/tests/selection.test.ts b/tests/selection.test.ts index 17fec51..df574ac 100644 --- a/tests/selection.test.ts +++ b/tests/selection.test.ts @@ -76,4 +76,21 @@ describe("global Escape selection routing", () => { expect(api.resolveEscapeSelectionScope?.("downloads", "allgemein", "DIV")).toBe("downloads"); expect(api.resolveEscapeSelectionScope?.("history", "accounts", "DIV")).toBe("history"); }); + + it("releases the focused account row when Escape leaves account selection", () => { + const api = selection as typeof selection & { + releaseAccountSelectionFocus?: (activeElement: { closest: (selector: string) => unknown; blur: () => void } | null) => boolean; + }; + let blurred = false; + const accountRow = { + closest: (selector: string) => selector === ".settings-account-row" ? {} : null, + blur: () => { blurred = true; } + }; + + expect(api.releaseAccountSelectionFocus).toBeTypeOf("function"); + expect(api.releaseAccountSelectionFocus?.(accountRow)).toBe(true); + expect(blurred).toBe(true); + expect(api.releaseAccountSelectionFocus?.({ closest: () => null, blur: () => {} })).toBe(false); + expect(api.releaseAccountSelectionFocus?.(null)).toBe(false); + }); });