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:
@@ -39,7 +39,7 @@ import {
|
|||||||
getProviderUsageDayKey
|
getProviderUsageDayKey
|
||||||
} from "../shared/provider-daily-limits";
|
} from "../shared/provider-daily-limits";
|
||||||
import { preservePackageOrderForDisplay, sortPackageOrderByName } from "./package-order";
|
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 { buildConfiguredProviderOrder, buildScopedAccountEnabledState, getAccountDialogSelectableOptions, matchesAccountModeFilter, pruneAccountRowSelections, resolveAccountStatusState, resolveAccountUsername, resolveVisibleAccountKind, runOptimisticAccountUpdate, updateAccountRowSelection } from "./account-ui";
|
||||||
import type { AccountModeFilter } from "./account-ui";
|
import type { AccountModeFilter } from "./account-ui";
|
||||||
import { buildAccountDeleteCommand, buildAccountReplaceCommand, buildAccountSecretRequest, createAccountEditState, validateAccountEdit } from "./account-edit";
|
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 (document.querySelector(".ctx-menu") || document.querySelector(".modal-backdrop")) return;
|
||||||
if (selectionScope === "downloads") setSelectedIds(new Set());
|
if (selectionScope === "downloads") setSelectedIds(new Set());
|
||||||
else if (selectionScope === "history") setSelectedHistoryIds(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) {
|
if (e.key === "Delete" && tabRef.current === "downloads" && selectedIds.size > 0) {
|
||||||
@@ -4179,7 +4182,7 @@ export function App(): ReactElement {
|
|||||||
window.addEventListener("keydown", onKey);
|
window.addEventListener("keydown", onKey);
|
||||||
window.addEventListener("mousedown", onDown);
|
window.addEventListener("mousedown", onDown);
|
||||||
return () => { window.removeEventListener("keydown", onKey); window.removeEventListener("mousedown", onDown); };
|
return () => { window.removeEventListener("keydown", onKey); window.removeEventListener("mousedown", onDown); };
|
||||||
}, [requestDeleteSelection, selectedIds, settingsSubTab]);
|
}, [requestDeleteSelection, selectedAccountRowKeys, selectedIds, settingsSubTab]);
|
||||||
|
|
||||||
const onExportBackup = async (): Promise<void> => {
|
const onExportBackup = async (): Promise<void> => {
|
||||||
closeMenus();
|
closeMenus();
|
||||||
|
|||||||
@@ -28,6 +28,16 @@ export function resolveEscapeSelectionScope(
|
|||||||
return view === "settings" && settingsSection === "accounts" ? "accounts" : null;
|
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.
|
* 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
|
* The selection set mixes package and item ids; when entries vanish (delta
|
||||||
|
|||||||
@@ -76,4 +76,21 @@ describe("global Escape selection routing", () => {
|
|||||||
expect(api.resolveEscapeSelectionScope?.("downloads", "allgemein", "DIV")).toBe("downloads");
|
expect(api.resolveEscapeSelectionScope?.("downloads", "allgemein", "DIV")).toBe("downloads");
|
||||||
expect(api.resolveEscapeSelectionScope?.("history", "accounts", "DIV")).toBe("history");
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user