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.
This commit is contained in:
Sucukdeluxe
2026-08-15 02:00:54 +02:00
parent b5854a2227
commit 90fc77f203
3 changed files with 33 additions and 3 deletions
+6 -3
View File
@@ -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<void> => {
closeMenus();
+10
View File
@@ -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
+17
View File
@@ -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);
});
});